diff --git a/.gitignore b/.gitignore index 6d7b9c17f7d3c0a133b2328a1c52005d3e39f75d..4a23ab3b2dfdfeee476d2a6c3d0f63bed253e8c0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ docs/_build/ # PyBuilder target/ +/examples/industrial-ai-apiserver/test.json +/examples/industrial-ai-apiserver/test.yaml diff --git a/examples/industrial-ai-apiserver/.dockerignore b/examples/industrial-ai-apiserver/.dockerignore deleted file mode 100644 index ecf5595b42bfed44860023b32dda892f3fae9b54..0000000000000000000000000000000000000000 --- a/examples/industrial-ai-apiserver/.dockerignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -.dockerignore -.gitignore -.drone.yml -README.md -**/*.pyc -**/.venv -**/__pycache__ -**/industrialai.egg-info -**/build diff --git a/examples/industrial-ai-apiserver/.drone.yml b/examples/industrial-ai-apiserver/.drone.yml old mode 100644 new mode 100755 index 6b329287c7901d822ac9ca9394c7944a24da4546..f1728baff87fa96520b01507a430ca8b78c450fc --- a/examples/industrial-ai-apiserver/.drone.yml +++ b/examples/industrial-ai-apiserver/.drone.yml @@ -1,70 +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 +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/examples/industrial-ai-apiserver/.gitignore b/examples/industrial-ai-apiserver/.gitignore old mode 100644 new mode 100755 index a101926a6b19def0f29644b2a2ef79a2d397af04..7e33bbd1a6f15aaa9ac0b257f7ad63adaba78c69 --- a/examples/industrial-ai-apiserver/.gitignore +++ b/examples/industrial-ai-apiserver/.gitignore @@ -1,72 +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 +# 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/examples/industrial-ai-apiserver/.gitreview b/examples/industrial-ai-apiserver/.gitreview old mode 100644 new mode 100755 index 05d5ef73046af5d3c196dc297c0bcf04369fee8f..aea3661373c75b5acd64f25144fe207a47f06d56 --- a/examples/industrial-ai-apiserver/.gitreview +++ b/examples/industrial-ai-apiserver/.gitreview @@ -1,4 +1,4 @@ -[gerrit] -host=review.sh.99cloud.net -port=29418 -project=fifth-element/industrial-ai-apiserver +[gerrit] +host=review.sh.99cloud.net +port=29418 +project=fifth-element/industrial-ai-apiserver diff --git a/examples/industrial-ai-apiserver/.stestr.conf b/examples/industrial-ai-apiserver/.stestr.conf old mode 100644 new mode 100755 index 3c04f30858fe06aba6c390d0bd36c248cc8d8425..7ded7b554a018cfdbd9086c504872894173f95b9 --- a/examples/industrial-ai-apiserver/.stestr.conf +++ b/examples/industrial-ai-apiserver/.stestr.conf @@ -1,4 +1,4 @@ -[DEFAULT] -test_path=./industrialai/tests -top_dir=./ -group_regex=gabbi\.(suitemaker|driver)\.(test_[^_]+_[^_]+) +[DEFAULT] +test_path=./industrialai/tests +top_dir=./ +group_regex=gabbi\.(suitemaker|driver)\.(test_[^_]+_[^_]+) diff --git a/examples/industrial-ai-apiserver/Dockerfile b/examples/industrial-ai-apiserver/Dockerfile old mode 100644 new mode 100755 index 68e04fabd829d8281c64e25bafe06378ad3edb0d..2f62f705076c4c9e485e984237891244ac766209 --- a/examples/industrial-ai-apiserver/Dockerfile +++ b/examples/industrial-ai-apiserver/Dockerfile @@ -1,16 +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"] +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/examples/industrial-ai-apiserver/MANIFEST.in b/examples/industrial-ai-apiserver/MANIFEST.in old mode 100644 new mode 100755 index c922f11ad7c4967578bf1ecaf226e64db9fe7982..d9c1be33dbd0f7bf9a256e545ae71eaa0785a1d8 --- a/examples/industrial-ai-apiserver/MANIFEST.in +++ b/examples/industrial-ai-apiserver/MANIFEST.in @@ -1 +1 @@ -recursive-include public * +recursive-include public * diff --git a/examples/industrial-ai-apiserver/README.md b/examples/industrial-ai-apiserver/README.md old mode 100644 new mode 100755 index fd4789cbe0afb99fa6c88a047ea97a25b45909c9..2f44e797c962bc7a888c98407f06d8000c8c18dc --- a/examples/industrial-ai-apiserver/README.md +++ b/examples/industrial-ai-apiserver/README.md @@ -1,61 +1,61 @@ -# 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/ -``` - -## 测试 - -```bash -python setup.py test -q - -stestr run - -tox -``` - -## API-doc - -### 安装 api-doc -1. 安装npm: - - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本 - - 在终端输入 npm -v ,输出npm版本号 -2. npm install apidoc –g -3. 参考: - - https://segmentfault.com/a/1190000011636199 - - https://apidocjs.com/#getting-started - - https://github.com/apidoc/apidoc - -### 生成接口文档 - -```bash -apidoc -i industrialai/api/controllers -o api-doc -``` - +# 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/ +``` + +## 测试 + +```bash +python setup.py test -q + +stestr run + +tox +``` + +## API-doc + +### 安装 api-doc +1. 安装npm: + - 前往 https://nodejs.org/en/ 下载node.js(集成npm)的最新版本 + - 在终端输入 npm -v ,输出npm版本号 +2. npm install apidoc –g +3. 参考: + - https://segmentfault.com/a/1190000011636199 + - https://apidocjs.com/#getting-started + - https://github.com/apidoc/apidoc + +### 生成接口文档 + +```bash +apidoc -i industrialai/api/controllers -o api-doc +``` + 点击 api-doc/index.html 访问 \ No newline at end of file diff --git a/examples/industrial-ai-apiserver/apidoc.json b/examples/industrial-ai-apiserver/apidoc.json old mode 100644 new mode 100755 index 37ac42c20295d0aeda937b4513895460ff8e5a7f..456dbfccc01ef4ab834681c5e8283e0040c34675 --- a/examples/industrial-ai-apiserver/apidoc.json +++ b/examples/industrial-ai-apiserver/apidoc.json @@ -1,8 +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 +{ + "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/examples/industrial-ai-apiserver/config.py b/examples/industrial-ai-apiserver/config.py old mode 100644 new mode 100755 index 6f9988d9777ee1b9b7d4e2bb68d0ff037ffb745c..c2965762842df9a303eceffdfdfe36437b32fd4a --- a/examples/industrial-ai-apiserver/config.py +++ b/examples/industrial-ai-apiserver/config.py @@ -1,54 +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 +# 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/examples/industrial-ai-apiserver/datainit.py b/examples/industrial-ai-apiserver/datainit.py old mode 100644 new mode 100755 index 64814c81c3aba30f492e88a71376c2f2120ab08a..193b1d245d614b339d2f0ab8d7620bb020f09d86 --- a/examples/industrial-ai-apiserver/datainit.py +++ b/examples/industrial-ai-apiserver/datainit.py @@ -1,28 +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\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" - engine = create_engine(DB_URL, echo=True) - Base.metadata.drop_all(engine) - Base.metadata.create_all(engine) - 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() +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\\23360\\Desktop\\dhuProject\\pecan-swagger\\examples\\industrial-ai-apiserver\\industrialai.db?check_same_thread=False" + engine = create_engine(DB_URL, echo=True) + Base.metadata.drop_all(engine) + Base.metadata.create_all(engine) + 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/examples/industrial-ai-apiserver/deploy/default.conf b/examples/industrial-ai-apiserver/deploy/default.conf old mode 100644 new mode 100755 index a903df6ba9ee8b023f8db2170fcbd82ee6843ead..1ff20a9c4a72ff46de7855d39aa24469abc8805a --- a/examples/industrial-ai-apiserver/deploy/default.conf +++ b/examples/industrial-ai-apiserver/deploy/default.conf @@ -1,44 +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; - #} -} +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/examples/industrial-ai-apiserver/deploy/nginx.conf b/examples/industrial-ai-apiserver/deploy/nginx.conf old mode 100644 new mode 100755 index 5e076aadc82d42f40a46a366d4bb69a52051016f..27034834193676f9654e267b566c7e496ff71015 --- a/examples/industrial-ai-apiserver/deploy/nginx.conf +++ b/examples/industrial-ai-apiserver/deploy/nginx.conf @@ -1,32 +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; -} + +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/examples/industrial-ai-apiserver/industrialai/__init__.py b/examples/industrial-ai-apiserver/industrialai/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/api/__init__.py b/examples/industrial-ai-apiserver/industrialai/api/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/api/app.py b/examples/industrial-ai-apiserver/industrialai/api/app.py old mode 100644 new mode 100755 index f03231acc2fa06482db7bd399b8b23521aac2ded..8f949eb9601df5b25f1d1ed83231c59d06cdf026 --- a/examples/industrial-ai-apiserver/industrialai/api/app.py +++ b/examples/industrial-ai-apiserver/industrialai/api/app.py @@ -1,14 +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 +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/examples/industrial-ai-apiserver/industrialai/api/controllers/__init__.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py old mode 100644 new mode 100755 index 6f596a50f655c1ad932973931ac8d7a9f4aee341..6adbba30a11f7cccee1ea235c2da5ac4f9a3fa83 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/root.py @@ -1,148 +1,157 @@ -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 pecan_swagger import decorators as swagger -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 - - -@swagger.path("/", "Root") -class RootController(object): - - _custom_actions = { - 'test': ['GET'], - 'signin':['POST'], - 'pwdcheck': ['POST'], - 'myself': ['POST'] - } - - @expose('json') - def test(self): - print("123") - return "test" - - @expose('json') - def index(self): - return None - - """ - @api {post} /signin signin - @apiName SignIn - @apiGroup User - - @apiBody {String} account 账号 - @apiBody {String} pwd 密码 - - @apiSuccess {String} token - @apiSuccessExample Success-Response: - { - "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. - eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ. - fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM" - } - - @apiError Unauthorized - @apiErrorExample {json} Error-Response: - { - "faultcode": "Client", - "faultstring": "This server could not verify that you are - authorized to access the document you requested. Either - you supplied the wrong credentials (e.g., bad password), - or your browser does not understand how to supply the - credentials required.", - "debuginfo": null - } - """ - @wsexpose(wtypes.text, body=logForm) - def signin(self, logForm): - account = logForm.account - 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 - } - - """ - @api {post} /pwdcheck check - @apiName PwdCheck - @apiGroup User - - @apiBody {Integer} id 用户id - @apiBody {String} pwd 密码 - - @apiSuccess {Bool} result - @apiSuccessExample Success-Response: - HTTP/1.1 OK 200 - { - "result": True True:校验通过/False:校验失败 - } - """ - @wsexpose(wtypes.text, body=checkForm) - def pwdcheck(self, checkForm): - try: - api.get_session().query(User). \ - filter_by(id=checkForm.id, pwd=checkForm.pwd).first() - except Exception: - return "Failure" - return "Success" - - """@api {get} /myself get_myself - @apiName GetMyself - @apiGroup User - - @apiSuccess (200) {Object} user - @apiSuccessExample Success-Response: - { - "id": 6, 用户id - "number": null, 身份证号 - "roleId": 3, 角色 - "status": 1, 状态:0/1 - "create_at": "2022-03-09 11:24:18.856936", 创建时间 - "name": "test", 名称 - "account": "testsss1-put", 账号 - "phone": "12345", 手机号 - } - """ - @expose('json') - def myself(self): - token = request.cookies.get("token") - 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() +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 pecan_swagger import decorators as swagger +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 + + +@swagger.definitions('object') +class registryForm: + account = wtypes.text + pwd = wtypes.text + phone = wtypes.text + mail = wtypes.text + name = wtypes.text + number = wtypes.text + check = checkForm() + + +@swagger.response('myself', '401', 'fail') +@swagger.response('index', '401', 'fail') +@swagger.response('test', '200', 'test description') +@swagger.method('test', 'index', 'signin', 'pwdcheck', 'myself') +@swagger.path("/", "Root") +class RootController(object): + _custom_actions = { + 'test': ['GET'], + 'signin': ['POST'], + 'pwdcheck': ['POST'], + 'myself': ['POST'] + } + + @expose('json') + def test(self): + print("123") + return "test" + + @expose('json') + def index(self): + return None + + """ + @api {post} /signin signin + @apiName SignIn + @apiGroup User + + @apiBody {String} account 账号 + @apiBody {String} pwd 密码 + + @apiSuccess {String} token + @apiSuccessExample Success-Response: + { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9. + eyJleHAiOjE2NDYzNzc2MjksInVzZXJJZCI6MSwiZ3JvdXBJZCI6MSwicm9sZUlkIjozfQ. + fEqaaS8c2A_i-2W9D_L76mJac2vcyoS51g6trXr34lM" + } + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either + you supplied the wrong credentials (e.g., bad password), + or your browser does not understand how to supply the + credentials required.", + "debuginfo": null + } + """ + + @wsexpose(wtypes.text, body=logForm) + def signin(self, logForm): + account = logForm.account + 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 + } + + """ + @api {post} /pwdcheck check + @apiName PwdCheck + @apiGroup User + + @apiBody {Integer} id 用户id + @apiBody {String} pwd 密码 + + @apiSuccess {Bool} result + @apiSuccessExample Success-Response: + HTTP/1.1 OK 200 + { + "result": True True:校验通过/False:校验失败 + } + """ + + @wsexpose(wtypes.text, body=checkForm) + def pwdcheck(self, checkForm): + try: + api.get_session().query(User). \ + filter_by(id=checkForm.id, pwd=checkForm.pwd).first() + except Exception: + return "Failure" + return "Success" + + """@api {get} /myself get_myself + @apiName GetMyself + @apiGroup User + + @apiSuccess (200) {Object} user + @apiSuccessExample Success-Response: + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + } + """ + + @expose('json') + def myself(self): + token = request.cookies.get("token") + 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/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/__init__.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py old mode 100644 new mode 100755 index ad47574c472d196d153f520f8246ea6de6e0fbb5..aa6a7e97e5dbb8c20e35064514b681d966f59c77 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/controller.py @@ -1,12 +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 +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/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py old mode 100644 new mode 100755 index 5c3ebe50b3979dcb62499d752380fc55d3c3afc1..8f31bd28048a4ea3f18e1bbbede629e8d4b83b40 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/role.py @@ -1,57 +1,59 @@ -from industrialai.api.expose import expose as wsexpose -from industrialai.pkg.page import get_page -from pecan import expose -from pecan import request -from pecan.rest import RestController -from pecan_swagger import decorators as swagger -from wsme import types as wstypes - - -class Roletest(wstypes.Base): - message = wstypes.text - roleId = int - - -@swagger.path("roles", "Role", "Root") -class RoleController(RestController): - _custom_actions = { - 'test': ['POST'] - } - - """ - @api {get} /v1/roles get_all_roles - @apiName GetAllRoles - @apiGroup Role - - @apiSuccess {Object[]} roles - @apiSuccessExample {type} Success-Response: - HTTP/1.1 200 OK - [ - “data":[ 角色列表 - { - "title": "expert", 角色名称 - "id": 1 角色id - }, - ] - "pages": 12 总页数 - “total": 10 总数 - ] - """ - - @expose('json') - def get_all(self): - 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 - } - - @wsexpose(Roletest, body=Roletest) - def test(self, role): - print(role) - role.message = "out" - role.roleId = 1 - print(role) - return role +from industrialai.api.expose import expose as wsexpose +from industrialai.pkg.page import get_page +from pecan import expose +from pecan import request +from pecan.rest import RestController +from pecan_swagger import decorators as swagger +from wsme import types as wstypes + + +class Roletest(wstypes.Base): + message = wstypes.text + roleId = int + + +@swagger.response('get_all', '401', 'fail') +@swagger.method('test', 'get_all') +@swagger.path("roles", "Role", "Root") +class RoleController(RestController): + _custom_actions = { + 'test': ['POST'] + } + + """ + @api {get} /v1/roles get_all_roles + @apiName GetAllRoles + @apiGroup Role + + @apiSuccess {Object[]} roles + @apiSuccessExample {type} Success-Response: + HTTP/1.1 200 OK + [ + “data":[ 角色列表 + { + "title": "expert", 角色名称 + "id": 1 角色id + }, + ] + "pages": 12 总页数 + “total": 10 总数 + ] + """ + + @expose('json') + def get_all(self): + 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 + } + + @wsexpose(Roletest, body=Roletest) + def test(self, role): + print(role) + role.message = "out" + role.roleId = 1 + print(role) + return role diff --git a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py old mode 100644 new mode 100755 index cdf98c36e83a00c12e7c3bb4cce0c4aa3a221b03..ce1ccb18dd189b44b34a917e54e82844f0ed30d5 --- a/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py +++ b/examples/industrial-ai-apiserver/industrialai/api/controllers/v1/user.py @@ -1,181 +1,203 @@ -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 -from pecan_swagger import decorators as swagger - -@swagger.response('get', '200', 'test description') -@swagger.method('get','get_one','post','put') -@swagger.path("users", "User", "Root") -class UserController(RestController): - """@api {get} /v1/users/?name= get_all_users - - @apiName GetAllUsers - @apiGroup User - - @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配) - @apiParam {Integer} status status of user 根据用户状态过滤 - @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配) - @apiParam {Integer} roleId role of user 根据角色进行过滤 - - @apiSuccess (200) {Object[]} users the list of user - @apiSuccessExample Success-Response: - { - "data": [ - { - "id": 6, 用户id - "number": null, 身份证号 - "roleId": 3, 角色 - "status": 1, 状态:0/1 - "create_at": "2022-03-09 11:24:18.856936", 创建时间 - "name": "test", 名称 - "account": "testsss1-put", 账号 - "phone": "12345", 手机号 - "email": null, 邮箱 - }, - ], - "pages": 10 总页数 - “total": 10 总数 - } - """ - - @expose("json") - def get(self, **kwargs): - 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 - } - - """@api {get} /v1/users/ get_one_user - @apiName GetOneUser - @apiGroup User - - @apiParam {Integer} userId id of user - - @apiSuccess (200) {Object} user - @apiSuccessExample Success-Response: - { - "id": 6, 用户id - "number": null, 身份证号 - "roleId": 3, 角色 - "status": 1, 状态:0/1 - "create_at": "2022-03-09 11:24:18.856936", 创建时间 - "name": "test", 名称 - "account": "testsss1-put", 账号 - "phone": "12345", 手机号 - "email": null, 邮箱 - } - """ - - @expose("json") - def get_one(self, user_id): - db_conn = request.db_conn - user = db_conn.get_user(user_id) - user.pwd = None - return user - - """@api {post} /v1/users create_user - @apiName CreateUser - @apiGroup User - - @apiBody {Object} user - @apiParamExample {json} Request-Example: - { - "user": { - "account": "testsss1-put", - "phone": "12345", - "email": xxx@xxxx.com, - "name": "test", - "pwd": "123456", - "roleId": 3 - } - } - - @apiSuccess {String} result - @apiSuccessExample Success-Response: - HTTP/1.1 200 OK - - @apiError Unauthorized - @apiErrorExample {json} Error-Response: - { - "faultcode": "Client", - "faultstring": "This server could not verify that you are - authorized to access the document you requested. Either you - supplied the wrong credentials (e.g., bad password), or your - browser does not understand how to supply the credentials - required.", - "debuginfo": null - } - """ - - @expose("json") - def post(self, user): - db_conn = request.db_conn - db_conn.add_user(user) - return "Success" - - """@api {put} /v1/users update_user - @apiName UpdateUser - @apiGroup User - - @apiBody {Object} user - @apiParamExample {json} Request-Example: - { - "user": { - "id": 6, - "enterpriseId": 1, - "enterpriseGroupId": 1, - "name": "test" - } - } - - @apiSuccess {String} result - @apiSuccessExample Success-Response: - "Success" - - @apiError Unauthorized - @apiErrorExample {json} Error-Response: - { - "faultcode": "Client", - "faultstring": "This server could not verify that you are - authorized to access the document you requested. Either you - supplied the wrong credentials (e.g., bad password), or your - browser does not understand how to supply the credentials - required.", - "debuginfo": null - } - """ - - @expose("json") - def put(self, user): - token = request.cookies.get("token") - 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" +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 +from pecan_swagger import decorators as swagger + + +@swagger.response('put', '401', 'fail') +@swagger.response('post', '401', 'fail') +@swagger.response('get_one', '401', 'fail') +@swagger.response('get', '401', 'fail') +@swagger.response('get', '200', 'test description') +@swagger.parameter('get', {'name': "status", + 'in': "query", + 'description': 'Status values that need to be considered for filter', + 'required': True, + 'type': 'array', + 'items': {'type': 'string', + 'enum': ["available", "pending", "sold"], + 'default': "available"}, + 'collectionFormat': "multi"}) +@swagger.operationId('get', 'get users') +@swagger.description('get', 'get users information') +@swagger.summary('get', 'get users information') +@swagger.tags('put', 'user') +@swagger.tags('post', 'user') +@swagger.tags('get', 'user') +@swagger.tags('get_one', 'user') +@swagger.method('get', 'get_one', 'post', 'put') +@swagger.path("users", "User", "Root") +class UserController(RestController): + """@api {get} /v1/users/?name= get_all_users + + @apiName GetAllUsers + @apiGroup User + + @apiParam {String} name name of user 根据名称进行查询(使用like进行匹配) + @apiParam {Integer} status status of user 根据用户状态过滤 + @apiParam {String} account account of user 根据账号进行查询(使用like进行匹配) + @apiParam {Integer} roleId role of user 根据角色进行过滤 + + @apiSuccess (200) {Object[]} users the list of user + @apiSuccessExample Success-Response: + { + "data": [ + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + "email": null, 邮箱 + }, + ], + "pages": 10 总页数 + “total": 10 总数 + } + """ + + + @expose("json") + def get(self, **kwargs): + 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 + } + + """@api {get} /v1/users/ get_one_user + @apiName GetOneUser + @apiGroup User + + @apiParam {Integer} userId id of user + + @apiSuccess (200) {Object} user + @apiSuccessExample Success-Response: + { + "id": 6, 用户id + "number": null, 身份证号 + "roleId": 3, 角色 + "status": 1, 状态:0/1 + "create_at": "2022-03-09 11:24:18.856936", 创建时间 + "name": "test", 名称 + "account": "testsss1-put", 账号 + "phone": "12345", 手机号 + "email": null, 邮箱 + } + """ + + @expose("json") + def get_one(self, user_id): + db_conn = request.db_conn + user = db_conn.get_user(user_id) + user.pwd = None + return user + + """@api {post} /v1/users create_user + @apiName CreateUser + @apiGroup User + + @apiBody {Object} user + @apiParamExample {json} Request-Example: + { + "user": { + "account": "testsss1-put", + "phone": "12345", + "email": xxx@xxxx.com, + "name": "test", + "pwd": "123456", + "roleId": 3 + } + } + + @apiSuccess {String} result + @apiSuccessExample Success-Response: + HTTP/1.1 200 OK + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either you + supplied the wrong credentials (e.g., bad password), or your + browser does not understand how to supply the credentials + required.", + "debuginfo": null + } + """ + + @expose("json") + def post(self, user): + db_conn = request.db_conn + db_conn.add_user(user) + return "Success" + + """@api {put} /v1/users update_user + @apiName UpdateUser + @apiGroup User + + @apiBody {Object} user + @apiParamExample {json} Request-Example: + { + "user": { + "id": 6, + "enterpriseId": 1, + "enterpriseGroupId": 1, + "name": "test" + } + } + + @apiSuccess {String} result + @apiSuccessExample Success-Response: + "Success" + + @apiError Unauthorized + @apiErrorExample {json} Error-Response: + { + "faultcode": "Client", + "faultstring": "This server could not verify that you are + authorized to access the document you requested. Either you + supplied the wrong credentials (e.g., bad password), or your + browser does not understand how to supply the credentials + required.", + "debuginfo": null + } + """ + + @expose("json") + def put(self, user): + token = request.cookies.get("token") + 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/examples/industrial-ai-apiserver/industrialai/api/expose.py b/examples/industrial-ai-apiserver/industrialai/api/expose.py old mode 100644 new mode 100755 index d84765769124001cb56290e6da81a8426b36a7ce..45219e2c365b6717bea1d5b47a25f5f577663686 --- a/examples/industrial-ai-apiserver/industrialai/api/expose.py +++ b/examples/industrial-ai-apiserver/industrialai/api/expose.py @@ -1,8 +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) +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/examples/industrial-ai-apiserver/industrialai/api/hooks.py b/examples/industrial-ai-apiserver/industrialai/api/hooks.py old mode 100644 new mode 100755 index 3395026958792575e46cd1ec52b036ec7b092f23..d3747626c577ed3245d758bdf153816c63151c9a --- a/examples/industrial-ai-apiserver/industrialai/api/hooks.py +++ b/examples/industrial-ai-apiserver/industrialai/api/hooks.py @@ -1,8 +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() +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/examples/industrial-ai-apiserver/industrialai/db/__init__.py b/examples/industrial-ai-apiserver/industrialai/db/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/db/api.py b/examples/industrial-ai-apiserver/industrialai/db/api.py old mode 100644 new mode 100755 index 3a6ecd3361894b539fc721fe5caadf31130a7fe8..7cce2e98dcdcb07ffc48794ac4055e4b4cb1d0ed --- a/examples/industrial-ai-apiserver/industrialai/db/api.py +++ b/examples/industrial-ai-apiserver/industrialai/db/api.py @@ -1,160 +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:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\" - "examples\\industrial-ai-apiserver\\" - "industrialai.db?check_same_thread=False " - ) - _ENGINE = create_engine(DB_URL, echo=True) - return _ENGINE - - -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 +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:///C:\\Users\\23360\\Desktop\\dhuProject\\pecan-swagger\\" + "examples\\industrial-ai-apiserver\\" + "industrialai.db?check_same_thread=False " + ) + _ENGINE = create_engine(DB_URL, echo=True) + return _ENGINE + + +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/examples/industrial-ai-apiserver/industrialai/db/model.py b/examples/industrial-ai-apiserver/industrialai/db/model.py old mode 100644 new mode 100755 index 3c7492cbc76778259de808a7fc694bb9d20e24a4..b95add2e8d95f721cf8de892156be2a1068b709c --- a/examples/industrial-ai-apiserver/industrialai/db/model.py +++ b/examples/industrial-ai-apiserver/industrialai/db/model.py @@ -1,33 +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)) +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/examples/industrial-ai-apiserver/industrialai/hacking/__init__.py b/examples/industrial-ai-apiserver/industrialai/hacking/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/hacking/checks.py b/examples/industrial-ai-apiserver/industrialai/hacking/checks.py old mode 100644 new mode 100755 index 84207e1d4850ccf05e35534f540aad30715c7aaa..0333ff11bd381cb6cff0b64fa9a2728826103ad3 --- a/examples/industrial-ai-apiserver/industrialai/hacking/checks.py +++ b/examples/industrial-ai-apiserver/industrialai/hacking/checks.py @@ -1,354 +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) +# 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/examples/industrial-ai-apiserver/industrialai/pkg/__init__.py b/examples/industrial-ai-apiserver/industrialai/pkg/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py b/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py old mode 100644 new mode 100755 index c9ceb2818f2b2e9dde8ceddd754ee09b5b3f2289..ea35837a6c248def6dc63d1fef44d859c7126367 --- a/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py +++ b/examples/industrial-ai-apiserver/industrialai/pkg/authentication.py @@ -1,40 +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 +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/examples/industrial-ai-apiserver/industrialai/pkg/const.py b/examples/industrial-ai-apiserver/industrialai/pkg/const.py old mode 100644 new mode 100755 index 74be40e98475d2cee64b46539a2d8b4442eb01a1..e494fccd13070108b724d24b052c29534c1a3522 --- a/examples/industrial-ai-apiserver/industrialai/pkg/const.py +++ b/examples/industrial-ai-apiserver/industrialai/pkg/const.py @@ -1,4 +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 +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/examples/industrial-ai-apiserver/industrialai/pkg/page.py b/examples/industrial-ai-apiserver/industrialai/pkg/page.py old mode 100644 new mode 100755 index a12ca5f9ebc952fc51d45ad84b8b19c2a492b066..9143919f665a8e5ca5521b1c2a69ebc01f74fcf8 --- a/examples/industrial-ai-apiserver/industrialai/pkg/page.py +++ b/examples/industrial-ai-apiserver/industrialai/pkg/page.py @@ -1,12 +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 +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/examples/industrial-ai-apiserver/industrialai/tests/__init__.py b/examples/industrial-ai-apiserver/industrialai/tests/__init__.py old mode 100644 new mode 100755 index bcdd1d79794fb7ba4a5454489f751b9a3979a2eb..5009142a86761432b24a2fa1e5579a67e0cf2bc3 --- a/examples/industrial-ai-apiserver/industrialai/tests/__init__.py +++ b/examples/industrial-ai-apiserver/industrialai/tests/__init__.py @@ -1,17 +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) +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/examples/industrial-ai-apiserver/industrialai/tests/config.py b/examples/industrial-ai-apiserver/industrialai/tests/config.py old mode 100644 new mode 100755 index 6a28f1a3d87c3797eef7f8bbc395fc50e7628efb..e92d42ee57f96169774b09614a8aee9ecad18582 --- a/examples/industrial-ai-apiserver/industrialai/tests/config.py +++ b/examples/industrial-ai-apiserver/industrialai/tests/config.py @@ -1,25 +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 +# 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/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py b/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py old mode 100644 new mode 100755 index d561f07ddcd6c9ce96c4cccd7560b5110a783dbc..0e10cc427788ce98df5c6820f89dc603cf5f7693 --- a/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py +++ b/examples/industrial-ai-apiserver/industrialai/tests/test_functional.py @@ -1,16 +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 +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/examples/industrial-ai-apiserver/industrialai/tests/test_units.py b/examples/industrial-ai-apiserver/industrialai/tests/test_units.py old mode 100644 new mode 100755 index 573fb682f821e0fb4800a96c80dd90bf4bca1c51..e73e711e66d8034cbff8a387a92cd7ea02895752 --- a/examples/industrial-ai-apiserver/industrialai/tests/test_units.py +++ b/examples/industrial-ai-apiserver/industrialai/tests/test_units.py @@ -1,7 +1,7 @@ -from unittest import TestCase - - -class TestUnits(TestCase): - - def test_units(self): - assert 5 * 5 == 25 +from unittest import TestCase + + +class TestUnits(TestCase): + + def test_units(self): + assert 5 * 5 == 25 diff --git a/examples/industrial-ai-apiserver/lower-constraints.txt b/examples/industrial-ai-apiserver/lower-constraints.txt old mode 100644 new mode 100755 index 63353dc5dbd597076923ae4e782bb8a1e5e5bb2f..40f8aa637e5c5d719c500eded96a159f9a25c909 --- a/examples/industrial-ai-apiserver/lower-constraints.txt +++ b/examples/industrial-ai-apiserver/lower-constraints.txt @@ -1,22 +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 +# 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/examples/industrial-ai-apiserver/myapp-doc.py b/examples/industrial-ai-apiserver/myapp-doc.py old mode 100644 new mode 100755 index 3ced6b9c0d2110e5a49850c8f538b6b1246808ce..88c51afba95579067a619fda6d3e64482db63ff5 --- a/examples/industrial-ai-apiserver/myapp-doc.py +++ b/examples/industrial-ai-apiserver/myapp-doc.py @@ -1,18 +1,24 @@ -#!/bin/env python -# import pprint -import json -from pecan_swagger import utils -import industrialai.api.controllers.root - -# pp = pprint.PrettyPrinter(indent=2) -# pp.pprint(utils.swagger_build('industrialai', '1.0')) -# Create JSON file -s=utils.swagger_build('industrialai', - '1.0', - 'This is a This is a test project', - 'http://swagger.io/terms/', - {'email': "apiteam@swagger.io"}) -json_str = json.dumps(s, indent=4) -f = open("test.json", "w") -f.write(json_str) -f.close() +# import pprint +import json +from pecan_swagger import utils +import industrialai.api.controllers.root + +# pp = pprint.PrettyPrinter(indent=2) +# pp.pprint(utils.swagger_build('industrialai', '1.0')) +# Create JSON file +s = utils.swagger_build('industrialai', '1.0') +description = 'This is a This is a test project' +termsOfService = 'http://swagger.io/terms/' +contact = {'email': "apiteam@swagger.io"} +license = {'name': "Apache 2.0", + 'url': "http://www.apache.org/licenses/LICENSE-2.0.html"} +s = utils.add_info(s, description, termsOfService, contact, license) +externalDocs = {'description': "Find out more about Swagger", + 'url': "http://swagger.io"} +s = utils.add_external_docs(s, externalDocs) +host = "127.0.0.1" +s = utils.add_host(s, host) +basePath = "/v1" +s = utils.add_base_path(s, basePath) +output_path = "test.json" +utils.print_json(s, output_path) diff --git a/examples/industrial-ai-apiserver/pecan_swagger/__init__.py b/examples/industrial-ai-apiserver/pecan_swagger/__init__.py old mode 100644 new mode 100755 diff --git a/examples/industrial-ai-apiserver/pecan_swagger/decorators.py b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py old mode 100644 new mode 100755 index a5ea799267ce4454e4aed77b9c149edf220a56ce..08de1584942739d632cec61f5d2c636b454e92df --- a/examples/industrial-ai-apiserver/pecan_swagger/decorators.py +++ b/examples/industrial-ai-apiserver/pecan_swagger/decorators.py @@ -1,62 +1,161 @@ -import pecan_swagger.g as g - -""" -decorators module - -these decorators are meant to be used by developers who wish to markup -their pecan applications to produce swagger. -""" - - -def path(endpoint, name, parent=None): - """ - path decorator - - this decorator should be used on pecan controllers to instruct how - they map to routes. - - :param endpoint: the root uri for this controller. - :param name: the name of this path. - :param parent: an optional path name to indicate a parent/child - relationship between this path and another. - """ - - def decorator(c): - if hasattr(c, '__swag'): - raise Exception('{} already has swag'.format(c.__name__)) - c.__swag = dict(endpoint=endpoint, name=name, parent=parent) - g.add_path(c) - return c - - return decorator - - -def response(method_name, res_code, des): - """ - 获取方法的response - 生成字典{response:res_code:{description:des}} - 并加入controller.__swag[method][method_name] - """ - - def decorator(m): - d = {} - d[res_code] = {'description': des} - m.__swag['method'][method_name]['response'] = d - return m - return decorator - -def method(*args): - """ - 获取被装饰类包含的方法 - 生成method字典 - 并加入controller.__swag - """ - - def decorator(m): - d = {} - for i in args: - if hasattr(m, i): - d[i] = {} - m.__swag['method'] = d - return m - return decorator +import pecan_swagger.g as g + +""" +decorators module + +these decorators are meant to be used by developers who wish to markup +their pecan applications to produce swagger. +""" + + +def path(endpoint, name, parent=None): + """ + path decorator + + this decorator should be used on pecan controllers to instruct how + they map to routes. + + :param endpoint: the root uri for this controller. + :param name: the name of this path. + :param parent: an optional path name to indicate a parent/child + relationship between this path and another. + """ + + def decorator(c): + if hasattr(c, '__swag'): + raise Exception('{} already has swag'.format(c.__name__)) + c.__swag = dict(endpoint=endpoint, name=name, parent=parent) + g.add_path(c) + return c + + return decorator + + +def response(method_name, res_code, des, schema=None): + """ + 获取方法的response + 生成字典{response:res_code:{description:des}} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + + if not m.__swag['method'][method_name].get('responses'): + m.__swag['method'][method_name]['responses'] = {} + if not m.__swag['method'][method_name]['responses'].get(res_code): + m.__swag['method'][method_name]['responses'][res_code] = {} + m.__swag['method'][method_name]['responses'][res_code]['description'] = des + if schema: + m.__swag['method'][method_name]['responses'][res_code]['schema'] = schema + return m + + return decorator + + +def tags(method_name, *tags): + """ + 获取方法的tags + 生成字典{tags:tag} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + if not m.__swag['method'][method_name].get('tags'): + m.__swag['method'][method_name]['tags'] = [] + for i in tags: + m.__swag['method'][method_name]['tags'].append(i) + return m + + return decorator + + +def summary(method_name, summary): + """ + 获取方法的summary + 生成字典{summary:summary} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['summary'] = summary + return m + + return decorator + + +def description(method_name, des): + """ + 获取方法的description + 生成字典{description:des} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['description'] = des + return m + + return decorator + + +def operationId(method_name, oi): + """ + 获取方法的operationId + 生成字典{operationId:oi} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + m.__swag['method'][method_name]['operationId'] = oi + return m + + return decorator + + +def method(*args): + """ + 获取被装饰类包含的方法 + 生成method字典 + 并加入controller.__swag + """ + + def decorator(m): + d = {} + for i in args: + if hasattr(m, i): + d[i] = {} + m.__swag['method'] = d + return m + + return decorator + + +def definitions(t, *args): + """ + 添加definitions到g.py中 + """ + + def decorator(m): + g.add_definitions(t, m) + return m + + return decorator + + +def parameter(method_name, *args): + """ + 获取方法的parameter + 生成字典{parameter:[]} + 并加入controller.__swag[method][method_name] + """ + + def decorator(m): + if not m.__swag['method'][method_name].get('parameters'): + m.__swag['method'][method_name]['parameters'] = [] + for i in args: + if i.get('schema'): + i['schema'] = {'$ref': '#/definitions/' + i['schema']} + m.__swag['method'][method_name]['parameters'].append(i) + return m + + return decorator + diff --git a/examples/industrial-ai-apiserver/pecan_swagger/g.py b/examples/industrial-ai-apiserver/pecan_swagger/g.py old mode 100644 new mode 100755 index b4869d13bd178bde3074567d586fcdc0cc10ba59..15f610405e3b349cff971e534609cfc901d180c0 --- a/examples/industrial-ai-apiserver/pecan_swagger/g.py +++ b/examples/industrial-ai-apiserver/pecan_swagger/g.py @@ -1,520 +1,617 @@ -import copy -import inspect - -from pecan import util as p_u -import wsme.types as wtypes -import six -import weakref - -""" -global hierarchy module - -this module is at the heart of the swagger conversion utility. it -contains a global "hierarchy" object which will provide a single point -to assemble an application's swagger output. - -there are also several helper functions to assist in building the -hierarchy of controllers, routes, and methods. -""" - -_hierarchy = {} - -_http_methods = {'get': '', 'post': '', 'put': '', - 'patch': '', 'delete': '', - 'head': '', 'trace': ''} - -_all_wsme_types = wtypes.native_types + ( - wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, - wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, - wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) - -_definitions = {} - - -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): - """获得wsexpose装饰方法的definitions.""" - - def datatype_to_type_and_format(datatype): - tf = {} - if datatype == 'uuid' or datatype == 'ipv4address' \ - or datatype == 'ipv6address' or datatype == 'date' \ - or datatype == 'time': - tf['type'] = 'string' - tf['format'] = datatype - elif datatype == 'datetime': - tf['type'] = 'string' - tf['format'] = 'date-time' - elif datatype == 'binary' or datatype == 'bytes': - # base64 encoded characters - tf['type'] = 'string' - tf['format'] = 'byte' - elif datatype == 'array' or datatype == 'boolean' \ - or datatype == 'integer' or datatype == 'string': - # no format - tf['type'] = datatype - elif datatype == 'float': - # number - tf['type'] = 'number' - tf['format'] = datatype - elif datatype == 'unicode' or datatype == 'str' \ - or datatype == 'text': - # primitive, no format - tf['type'] = 'string' - elif datatype == 'int' or datatype == 'decimal': - # primitive, no format - tf['type'] = 'integer' - elif datatype == 'bool': - # primitive, no format - tf['type'] = 'boolean' - elif datatype == 'enum': - tf['type'] = 'enum' - elif datatype == 'unset' or datatype == 'none': - tf['type'] = None - elif datatype == 'dict': - tf['type'] = 'object' - else: - tf['type'] = 'object' - return tf - - def class_to_name_str(c): - return (('%s' % c).replace('<', '').replace('>', '') - .replace('class ', '').replace('\'', '') - .split(' ', 1)[0].rsplit('.', 1)[-1]) - - def conv_class_name_to_type_str(cn): - type_str = '' - if cn.endswith('Type'): - type_str = cn[:-4] - elif cn == 'str': - type_str = 'string' - else: - type_str = cn - type_str = type_str.lower() - return type_str - - def get_type_str(obj, src_dict=None): - type_str = '' - if hasattr(obj, '__name__'): - type_str = obj.__name__ - else: - type_str = obj.__class__.__name__ - type_str = conv_class_name_to_type_str(type_str) - - tf = datatype_to_type_and_format(type_str) - - if hasattr(obj, 'basetype') and \ - (obj.__class__ not in _all_wsme_types or type_str == 'enum'): - # UserType or Enum - tf['type'] = get_type_str(obj.basetype) - - if isinstance(src_dict, dict): - # if dict is in args, set 'type' and 'format' into the dict and - # return - src_dict.update({'type': tf['type']}) - if 'format' in tf: - src_dict.update({'format': tf['format']}) - - # get datatype options. ex.) min_length, minimum, .. - for k, v in inspect.getmembers(obj): - if ((k == 'minimum' or k == 'maxmum' - or k == 'min_length' or k == 'max_length') - and v is not None): - src_dict[to_swag_key(k)] = v - elif k == 'pattern' and v is not None: - src_dict[to_swag_key(k)] = v.pattern - # TODO(shu-mutou): this should be removed for production. - # else: - # src_dict[to_swag_key(k)] = v - - if type_str == 'enum': - # EnumType use 'set' that doesn't have sequence. - # So use 'sorted' for keeping static output. - src_dict['enum'] = sorted([v for v in obj.values]) - - # return 'type' only - return tf['type'] - - def to_swag_key(key): - keys = { - 'doc': 'description', - 'arguments': 'parameters', - 'return_type': 'schema', - 'datatype': 'type', - 'mandatory': 'required', - 'sample': 'examples', - 'readonly': 'readOnly', - 'min_length': 'minLength', - 'max_length': 'maxLength', - } - if key in keys: - return keys[key] - else: - return key - - def get_wm_item_prop(item, isparams=False): - # Add prop into 'properties' and 'required' in 'Items Object' - # 'Items Object' can be part of 'Schema Object' or 'Items Object', - # and can use recursively - prop_dict = {} - # TODO(shu-mutou): this should be removed for production. - # prop_dict['obj'] = inspect.getmembers(item) - - _item = item - if wtypes.iscomplex(item): - _item = weakref.ref(item) - - for a, i in inspect.getmembers(_item): - if a == 'datatype': - datatype = get_type_str(i, prop_dict) - if datatype == 'array': - # if array, do recursively - prop_dict['items'] = inspect_wm_schema(i.item_type) - elif datatype == 'object': - # if obuject, do recursively - prop_dict['items'] = inspect_wm_schema(i) - elif a == 'default' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'name' and isparams: - prop_dict[to_swag_key(a)] = i - elif a == 'mandatory' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'readonly' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'doc' and i is not None: - prop_dict[to_swag_key(a)] = i - - if isparams and prop_dict['type'] in ['object', 'array']: - prop_dict['schema'] = {'items': prop_dict['items'], - 'type': prop_dict['type']} - 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) - paths = get_controller_paths(controllers, combine_defs(name)) - 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') and cfg.get('generic_handlers').get('DEFAULT'): - mlist.append('get') - if cfg.get('allowed_methods'): - mlist += cfg.get('allowed_methods') - if hasattr(c, '__swag') and c.__swag.get('method'): - for i in c.__swag['method']: - mlist.append(i) - # for i in c.__dict__: - # ii = getattr(c, i) - # if hasattr(ii, '__swag'): - # m = ii.__swag.get('method') - # if m is not None: - # mlist.append(m) - return mlist - - -def path_join(part1, part2): - """join two url paths.""" - if len(part2) == 0: - return part1 - sep = '/' - if part1[-1] == sep: - sep = '' - return part1 + sep + part2 - - -def get_def(name): - """获取所有expose装饰方法的definitions.""" - c = _hierarchy[name] - if hasattr(c, '__swag') and c.__swag.get('method'): - return c.__swag['method'] - return {} - - -def combine_defs(name): - """合并expose和wsexpose装饰方法的definitions.""" - - c = _hierarchy[name] - lc = copy.deepcopy(c) - w_d = get_wsme_defs(name) - e_d = get_def(name) - if w_d and e_d: - for i in w_d: - if e_d.get(i): - for j in w_d[i]: - e_d[i][j]=w_d[i][j] - else: - e_d[i]=w_d[i] - elif w_d: - return w_d - else: - return e_d - return e_d +import copy +import inspect + +from pecan import util as p_u +import wsme.types as wtypes +import six +import weakref + +""" +global hierarchy module + +this module is at the heart of the swagger conversion utility. it +contains a global "hierarchy" object which will provide a single point +to assemble an application's swagger output. + +there are also several helper functions to assist in building the +hierarchy of controllers, routes, and methods. +""" + +_hierarchy = {} + +_http_methods = {'get': '', 'post': '', 'put': '', + 'patch': '', 'delete': '', + 'head': '', 'trace': ''} + +_all_wsme_types = wtypes.native_types + ( + wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, + wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, + wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) + +_definitions = {} + +all_definitions = {} + + +def add_path(c): + """adds a named controller to the hierarchy.""" + if _hierarchy.get(c.__swag['name']): + raise Exception( + 'name {} already exists in hierarchy'.format(c.__swag['name'])) + _hierarchy[c.__swag['name']] = c + + +def build_path(swaginfo): + """return the route path for a swag metadata item.""" + if swaginfo.get('parent') is not None: + return path_join(build_path(get_swag(swaginfo.get('parent'))), + swaginfo.get('endpoint')) + return swaginfo.get('endpoint') + + +def get_controller_paths(controllers, wsme_defs): + """ + get a list of paths with methods + + returns a list of tuples (controller path, methods). + """ + + def get_methods_for_generic(name): + methods = [] + generic_handlers = lc.get(name).get('generic_handlers', {}) + for method, func in generic_handlers.items(): + if method == 'DEFAULT': + methods.append('get') + else: + methods.append(method.lower()) + # TODO drill down through decorators to get true function name + truename = getrealname(func) + del lc[truename] + return methods + + def append_methods_for_specific(name, tpl): + paths.append(tpl) + del lc[name] + + lc = copy.deepcopy(controllers) + paths = [] + # TODO incorporate method decorator, removing functions marked + if lc.get('index'): + for method in get_methods_for_generic('index'): + paths.append(('', (method, {}))) + spec = wsme_defs.get('index') # add wsme definitions + append_methods_for_specific('index', ('', ('get', spec))) + + # for REST controller + for method, path in _http_methods.items(): + if lc.get(method): + spec = wsme_defs.get(method, {}) # add wsme definitions + append_methods_for_specific(method, (path, (method, spec))) + + if lc.get('get_all'): + # add wsme definitions + spec = wsme_defs.get('get_all') # add wsme definitions + append_methods_for_specific('get_all', ('get_all', ('get', spec))) + if lc.get('get_one'): + # add wsme definitions + spec = wsme_defs.get('get_one') # add wsme definitions + append_methods_for_specific('get_one', ('get_one', ('get', spec))) + + if lc.get('_default'): + append_methods_for_specific('_default', ('', ('*', {}))) + if lc.get('_route'): + append_methods_for_specific('_route', ('', ('*', {}))) + if lc.get('_lookup'): + del lc['_lookup'] + + if lc.get('_custom_actions'): + for ca, ca_method in lc['_custom_actions'].items(): + spec = wsme_defs.get(ca) # add wsme definitions + for m in ca_method: + paths.append((ca, (m.lower(), spec))) + del lc['_custom_actions'] + + generic_controllers = [c for c in lc if lc[c].get('generic')] + for controller in generic_controllers: + for method in get_methods_for_generic(controller): + paths.append((controller, (method, {}))) + for controller in lc: + if controller not in [path[0] for path in paths]: + paths.append((controller, ('get', {}))) + return paths + + +def get_wsme_defs(name): + """获得wsexpose装饰方法的definitions.""" + + def datatype_to_type_and_format(datatype): + tf = {} + if datatype == 'uuid' or datatype == 'ipv4address' \ + or datatype == 'ipv6address' or datatype == 'date' \ + or datatype == 'time': + tf['type'] = 'string' + tf['format'] = datatype + elif datatype == 'datetime': + tf['type'] = 'string' + tf['format'] = 'date-time' + elif datatype == 'binary' or datatype == 'bytes': + # base64 encoded characters + tf['type'] = 'string' + tf['format'] = 'byte' + elif datatype == 'array' or datatype == 'boolean' \ + or datatype == 'integer' or datatype == 'string': + # no format + tf['type'] = datatype + elif datatype == 'float': + # number + tf['type'] = 'number' + tf['format'] = datatype + elif datatype == 'unicode' or datatype == 'str' \ + or datatype == 'text': + # primitive, no format + tf['type'] = 'string' + elif datatype == 'int' or datatype == 'decimal': + # primitive, no format + tf['type'] = 'integer' + elif datatype == 'bool': + # primitive, no format + tf['type'] = 'boolean' + elif datatype == 'enum': + tf['type'] = 'enum' + elif datatype == 'unset' or datatype == 'none': + tf['type'] = None + elif datatype == 'dict': + tf['type'] = 'object' + else: + tf['type'] = 'object' + return tf + + def class_to_name_str(c): + return (('%s' % c).replace('<', '').replace('>', '') + .replace('class ', '').replace('\'', '') + .split(' ', 1)[0].rsplit('.', 1)[-1]) + + def conv_class_name_to_type_str(cn): + type_str = '' + if cn.endswith('Type'): + type_str = cn[:-4] + elif cn == 'str': + type_str = 'string' + else: + type_str = cn + type_str = type_str.lower() + return type_str + + def get_type_str(obj, src_dict=None): + type_str = '' + if hasattr(obj, '__name__'): + type_str = obj.__name__ + else: + type_str = obj.__class__.__name__ + type_str = conv_class_name_to_type_str(type_str) + + tf = datatype_to_type_and_format(type_str) + + if hasattr(obj, 'basetype') and \ + (obj.__class__ not in _all_wsme_types or type_str == 'enum'): + # UserType or Enum + tf['type'] = get_type_str(obj.basetype) + + if isinstance(src_dict, dict): + # if dict is in args, set 'type' and 'format' into the dict and + # return + src_dict.update({'type': tf['type']}) + if 'format' in tf: + src_dict.update({'format': tf['format']}) + + # get datatype options. ex.) min_length, minimum, .. + for k, v in inspect.getmembers(obj): + if ((k == 'minimum' or k == 'maxmum' + or k == 'min_length' or k == 'max_length') + and v is not None): + src_dict[to_swag_key(k)] = v + elif k == 'pattern' and v is not None: + src_dict[to_swag_key(k)] = v.pattern + # TODO(shu-mutou): this should be removed for production. + # else: + # src_dict[to_swag_key(k)] = v + + if type_str == 'enum': + # EnumType use 'set' that doesn't have sequence. + # So use 'sorted' for keeping static output. + src_dict['enum'] = sorted([v for v in obj.values]) + + # return 'type' only + return tf['type'] + + def to_swag_key(key): + keys = { + 'doc': 'description', + 'arguments': 'parameters', + 'return_type': 'schema', + 'datatype': 'type', + 'mandatory': 'required', + 'sample': 'examples', + 'readonly': 'readOnly', + 'min_length': 'minLength', + 'max_length': 'maxLength', + } + if key in keys: + return keys[key] + else: + return key + + def get_wm_item_prop(item, isparams=False): + # Add prop into 'properties' and 'required' in 'Items Object' + # 'Items Object' can be part of 'Schema Object' or 'Items Object', + # and can use recursively + prop_dict = {} + # TODO(shu-mutou): this should be removed for production. + # prop_dict['obj'] = inspect.getmembers(item) + + _item = item + if wtypes.iscomplex(item): + _item = weakref.ref(item) + + for a, i in inspect.getmembers(_item): + if a == 'datatype': + datatype = get_type_str(i, prop_dict) + if datatype == 'array': + # if array, do recursively + prop_dict['items'] = inspect_wm_schema(i.item_type) + elif datatype == 'object': + # if obuject, do recursively + prop_dict['items'] = inspect_wm_schema(i) + elif a == 'default' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'name' and isparams: + prop_dict[to_swag_key(a)] = i + elif a == 'mandatory' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'readonly' and i: + prop_dict[to_swag_key(a)] = i + elif a == 'doc' and i is not None: + prop_dict[to_swag_key(a)] = i + + if isparams and prop_dict['type'] in ['object', 'array']: + prop_dict['schema'] = {'items': prop_dict['items'], + 'type': prop_dict['type']} + if prop_dict['type'] == 'object': + prop_dict['schema'] = prop_dict['items'] + del prop_dict['type'] + del prop_dict['items'] + return prop_dict + + def get_wsattr_and_wsproperty(obj): + ws_dict = {} + for key, value in inspect.getmembers(obj): + if (key[0] != '_' and + (value.__class__.__name__ == 'wsattr' + or value.__class__.__name__ == 'wsproperty')): + ws_dict[key] = value + return ws_dict + + def inspect_wm_schema(schema_obj, isparams=False): + schema_dict = {} + # TODO(shu-mutou): this should be removed for production. + # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj) + ws_len = len(get_wsattr_and_wsproperty(schema_obj)) + model_name = class_to_name_str(schema_obj) + + for key, obj in inspect.getmembers(schema_obj): + if (key[0] != '_' and + (obj.__class__.__name__ == 'wsattr' + or obj.__class__.__name__ == 'wsproperty')): + # TODO(shu-mutou): this should be removed for production. + # _definitions[model_name][to_swag_key(key)] = \ + # {'obj': inspect.getmembers(obj)} + + if ws_len == 1: + # single schema + schema_dict = get_wm_item_prop(obj, isparams) + else: + # multi property schema + schema_dict.update({'type': 'object'}) + # properties + if 'properties' not in schema_dict: + schema_dict['properties'] = {} + prop = {key: get_wm_item_prop(obj, isparams)} + # required as array of string + if 'required' in prop[key] and prop[key]['required'] \ + and isinstance(prop[key]['required'], bool): + if 'required' not in schema_dict: + schema_dict['required'] = [] + schema_dict['required'].append(key) + del prop[key]['required'] + schema_dict['properties'].update(prop) + + if schema_obj not in _all_wsme_types: + if model_name not in _definitions: + _definitions[model_name] = schema_dict + schema_dict = {'$ref': "#/definitions/%s" % model_name} + + return schema_dict + + def get_wm_def(o): + wsme = {'description': ''} + for w, d in inspect.getmembers(o): + if w == 'arguments': + wsme[to_swag_key(w)] = [] + for arg in d: + # set each 'Parameter Object', it can include + # 'Items Object' recursively + item_dict = get_wm_item_prop(arg, True) + # TODO: MUST be set one of + # 'body|query|path|header|formData' + item_dict['in'] = 'query' + wsme[to_swag_key(w)].append(item_dict) + # 'body' should be set due to existing 'body' option + # in WSME expose + if o.body_type is not None: + # if body is set, last arg is it. + wsme[to_swag_key(w)][-1]['in'] = 'body' + elif w == 'doc' and d: + wsme[to_swag_key(w)] = d + elif w == 'return_type': + wsme['responses'] = {'status': {'description': ''}} + if d: + if d in _all_wsme_types: + # d is set single WSME type class or implicit type + wsme['responses']['status'][to_swag_key(w)] = ( + datatype_to_type_and_format( + conv_class_name_to_type_str( + class_to_name_str(d)))) + else: + # d is model class + wsme['responses']['status'][to_swag_key(w)] = ( + inspect_wm_schema(d, False)) + doc = inspect.getdoc(d) + if doc is not None: + wsme['responses']['status']['description'] = doc + elif w == 'status_code': + wsme['responses'][d] = wsme['responses']['status'] + del wsme['responses']['status'] + # TODO(shu-mutou): this should be removed for production. + # elif w == 'body_type': + # wsme[to_swag_key(w)] = get_type_str(d) + # elif w == 'extra_options' or w == 'ignore_extra_args' \ + # or w == 'name' or w == 'pass_request': + # wsme[to_swag_key(w)] = d + # else: + # wsme[to_swag_key(w)] = d + return wsme + + c = _hierarchy[name] + wsme_defs = {} + for k, v in inspect.getmembers(c): + if p_u.iscontroller(v): + for m, o in inspect.getmembers(v): + if m == "_wsme_definition": + wsme_defs[k] = get_wm_def(o) + + # TODO(shu-mutou): this should be removed for production. + # output wsme info into files by each controller for dev + # import pprint + # with open(name + '.txt', 'w') as fout: + # pprint.pprint(wsme_defs, stream=fout, indent=2) + + return wsme_defs + + +def get_controllers(name): + """ + get all the controllers associated with a path + + returns a dictionary of controllers indexed by their names. + """ + c = _hierarchy[name] + controllers = {k: p_u._cfg(v) + for k, v in c.__dict__.items() if p_u.iscontroller(v)} + cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'} + if len(cacts): + controllers.update(cacts) + return controllers + + +def get_paths(): + """ + return all the registered paths + + loops through the hierarchy and retuns a list of tuples containing the + paths and their methods. + + :returns: [(path, methods), ...] + """ + pathlist = [] + for name in _hierarchy: + fullpath = build_path(get_swag(name)) + controllers = get_controllers(name) + paths = get_controller_paths(controllers, combine_method_defs(name)) + for path in paths: + ptuple = (path_join(fullpath, path[0]), path[1]) + pathlist.append(ptuple) + combine_all_definitions() + return pathlist + + +def get_swag(name): + """return the swag metadata from an named controller.""" + return _hierarchy.get(name).__swag + + +def getrealname(method): + """attempt to get a method's real name.""" + argspec = inspect.getargspec(method) + args = argspec[0] + if args and args[0] == 'self': + return method.__name__ + if hasattr(method, '__func__'): + method = method.__func__ + + func_closure = six.get_function_closure(method) + + # NOTE(sileht): if the closure is None we cannot look deeper, + # so return actual argspec, this occurs when the method + # is static for example. + if func_closure is None: + return method.__name__ + + closure = next( + ( + c for c in func_closure if six.callable(c.cell_contents) + ), + None + ) + method = closure.cell_contents + return getrealname(method) + + +def methods_get(name): + """get all the methods for a named controller.""" + c = _hierarchy[name] + mlist = [] + if hasattr(c, 'index') and p_u.iscontroller(c.index): + cfg = p_u._cfg(c.index) + if cfg.get('generic_handlers') and cfg.get('generic_handlers').get('DEFAULT'): + mlist.append('get') + if cfg.get('allowed_methods'): + mlist += cfg.get('allowed_methods') + if hasattr(c, '__swag') and c.__swag.get('method'): + for i in c.__swag['method']: + mlist.append(i) + # for i in c.__dict__: + # ii = getattr(c, i) + # if hasattr(ii, '__swag'): + # m = ii.__swag.get('method') + # if m is not None: + # mlist.append(m) + return mlist + + +def path_join(part1, part2): + """join two url paths.""" + if len(part2) == 0: + return part1 + sep = '/' + if part1[-1] == sep: + sep = '' + return part1 + sep + part2 + + +def get_def(name): + """获取所有expose装饰方法的definitions.""" + c = _hierarchy[name] + if hasattr(c, '__swag') and c.__swag.get('method'): + return c.__swag['method'] + return {} + + +def combine_method_defs(name): + """合并expose和wsexpose装饰方法的definitions.""" + + c = _hierarchy[name] + lc = copy.deepcopy(c) + w_d = get_wsme_defs(name) + e_d = get_def(name) + if w_d and e_d: + for i in w_d: + if e_d.get(i): + for j in w_d[i]: + e_d[i][j] = w_d[i][j] + else: + e_d[i] = w_d[i] + elif w_d: + return w_d + else: + return e_d + return e_d + + +def format_type(t): + """ + 判断数据类型是否为自定义的数据类型 + """ + + def datatype_to_type_and_format(datatype): + + tf = {} + if datatype == 'uuid' or datatype == 'ipv4address' \ + or datatype == 'ipv6address' or datatype == 'date' \ + or datatype == 'time': + tf['type'] = 'string' + tf['format'] = datatype + elif datatype == 'datetime': + tf['type'] = 'string' + tf['format'] = 'date-time' + elif datatype == 'binary' or datatype == 'bytes': + # base64 encoded characters + tf['type'] = 'string' + tf['format'] = 'byte' + elif datatype == 'array' or datatype == 'boolean' \ + or datatype == 'integer' or datatype == 'string': + # no format + tf['type'] = datatype + elif datatype == 'float': + # number + tf['type'] = 'number' + tf['format'] = datatype + elif datatype == 'unicode' or datatype == 'str' \ + or datatype == 'text': + # primitive, no format + tf['type'] = 'string' + elif datatype == 'int' or datatype == 'decimal': + # primitive, no format + tf['type'] = 'integer' + elif datatype == 'bool': + # primitive, no format + tf['type'] = 'boolean' + elif datatype == 'enum': + tf['type'] = 'enum' + elif datatype == 'unset' or datatype == 'none': + tf['type'] = None + elif datatype == 'dict': + tf['type'] = 'object' + else: + tf['$ref'] = '#/definitions/' + datatype + return tf + + def conv_class_name_to_type_str(cn): + type_str = '' + if cn.endswith('Type'): + type_str = cn[:-4] + elif cn == 'str': + type_str = 'string' + else: + type_str = cn + type_str = type_str + return type_str + + def class_to_name_str(c): + return (('%s' % c).replace('<', '').replace('>', '') + .replace('class ', '').replace('\'', '') + .split(' ', 1)[0].rsplit('.', 1)[-1]) + + return datatype_to_type_and_format(conv_class_name_to_type_str(class_to_name_str(t))) + + +def add_definitions(t, m): + """将定义的类添加到all_definitions.""" + + if not all_definitions.get(m.__name__): + d = {} + d['type'] = t + d['properties'] = {} + for i, j in inspect.getmembers(m): + if not i.endswith('__'): + d['properties'][i] = format_type(j) + all_definitions[m.__name__] = d + + +def combine_all_definitions(): + """合并自定义和wsexpose解析出的definitions.""" + + for i in all_definitions: + if not _definitions.get(i): + _definitions[i] = all_definitions[i] + + return _definitions diff --git a/examples/industrial-ai-apiserver/pecan_swagger/utils.py b/examples/industrial-ai-apiserver/pecan_swagger/utils.py old mode 100644 new mode 100755 index 32ffa39cf351decc225f3ef526fa1244fb645b2d..889fbf4d19bf24a2948c156be442f15db5ec7ccd --- a/examples/industrial-ai-apiserver/pecan_swagger/utils.py +++ b/examples/industrial-ai-apiserver/pecan_swagger/utils.py @@ -1,73 +1,108 @@ -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, description=None, termsOfService=None, contact=None, license=None): - swag = dict() - swag['swagger'] = '2.0' - swag['info'] = dict(title=title, - version=version, - description=description, - termsOfService=termsOfService, - contact=contact, - license=license) - swag['consumes'] = [] - swag['produces'] = [] - swag['paths'] = {} - swag['host'] = [] - swag['basePath'] = [] - swag['tags'] = [] - swag['schemes'] = [] - for p in g.get_paths(): - if p[0] not in swag['paths']: - swag['paths'][p[0]] = _tuple_to_dict(p[1]) - elif len(p[1]) > 0: - swag['paths'][p[0]].update(_tuple_to_dict(p[1])) - swag['definitions'] = g._definitions - swag['externalDocs'] = [] - 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 +from pecan_swagger import g as g +import json +import yaml + +""" +utility function module + +this module contains the utility functions to assemble the swagger +dictionary object. they can be consumed by end-user applications to +build up swagger objects for applications. + +functions: +swagger_build -- build a full swagger dictionary +""" + + +def swagger_build(title, version): + swag = dict() + swag['swagger'] = '2.0' + swag['info'] = dict(title=title, version=version) + swag['consumes'] = [] + swag['produces'] = [] + swag['paths'] = {} + swag['tags'] = [] + swag['schemes'] = [] + for p in g.get_paths(): + if p[0] not in swag['paths']: + swag['paths'][p[0]] = _tuple_to_dict(p[1]) + elif len(p[1]) > 0: + swag['paths'][p[0]].update(_tuple_to_dict(p[1])) + swag['definitions'] = g._definitions + return swag + + +def print_json(s, output_path): + json_str = json.dumps(s, indent=4) + f = open(output_path, "w") + f.write(json_str) + f.close() + + +def print_yaml(s, output_path): + yaml_str = yaml.dump(s, indent=4) + f = open(output_path, "w") + f.write(yaml_str) + f.close() + + +def add_info(s, description=None, terms_of_service=None, contact=None, license=None): + if description is not None: + s['info']['description'] = description + if terms_of_service is not None: + s['info']['termsOfService'] = terms_of_service + if contact is not None: + s['info']['contact'] = contact + if license is not None: + s['info']['license'] = license + return s + + +def add_external_docs(s, external_docs): + s["externalDocs"] = external_docs + return s + + +def add_host(s, host): + s["host"] = host + return s + + +def add_base_path(s, basepath): + s["basePath"] = basepath + return s + + +def _tuple_to_dict(tpl): + """Convert tuple to dictionary + + each tuple must have key and value. + This function arrows taple including lists. + + ex.) acceptable taple + OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name'])))) + NG: ('/', ('get',('desc','res',('params',['id','name'])))) + """ + if isinstance(tpl, list): + d = [] + for e in tpl: + d.append(_tuple_to_dict(e)) + elif isinstance(tpl, tuple): + d = {} + if isinstance(tpl[0], tuple): + # tuple has some child tuple + for e in tpl: + d[e[0]] = _tuple_to_dict(e[1]) + elif isinstance(tpl[0], list): + # list member should be processed recursively + d = _tuple_to_dict(tpl[0]) + else: + if len(tpl) == 2: + # single tuple node + d[tpl[0]] = _tuple_to_dict(tpl[1]) + else: + raise Exception(tpl) + else: + # value or dict + d = tpl + return d diff --git a/examples/industrial-ai-apiserver/requirements.txt b/examples/industrial-ai-apiserver/requirements.txt old mode 100644 new mode 100755 index 3be40053a63a715a1241b6b8cf472fff02a4f152..7c25033683dd8e7e2f6008553f94debfdf655e53 --- a/examples/industrial-ai-apiserver/requirements.txt +++ b/examples/industrial-ai-apiserver/requirements.txt @@ -1,7 +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 +# 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/examples/industrial-ai-apiserver/run.py b/examples/industrial-ai-apiserver/run.py old mode 100644 new mode 100755 index 292365cb70b39f2f91d20b934324fbf5938761b8..d7d8fdc8d5bc02e47d9466135544a73f7ec722de --- a/examples/industrial-ai-apiserver/run.py +++ b/examples/industrial-ai-apiserver/run.py @@ -1,20 +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() +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/examples/industrial-ai-apiserver/setup.cfg b/examples/industrial-ai-apiserver/setup.cfg old mode 100644 new mode 100755 index ab44f5c73171af76868c37a53863dfdac778618c..3ca45f5dbc06bb85596ee822c8042992f7a64b67 --- a/examples/industrial-ai-apiserver/setup.cfg +++ b/examples/industrial-ai-apiserver/setup.cfg @@ -1,6 +1,6 @@ -[nosetests] -match=^test -where=industrialai -nocapture=1 -cover-package=industrialai -cover-erase=1 +[nosetests] +match=^test +where=industrialai +nocapture=1 +cover-package=industrialai +cover-erase=1 diff --git a/examples/industrial-ai-apiserver/setup.py b/examples/industrial-ai-apiserver/setup.py old mode 100644 new mode 100755 index 1c8bb6be781e8d99d82d8efe19bb0a422cd7e07f..2d5ba3f9f62179d481e02ccfc6515ef90d6b3c15 --- a/examples/industrial-ai-apiserver/setup.py +++ b/examples/industrial-ai-apiserver/setup.py @@ -1,22 +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']) -) +# -*- 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/examples/industrial-ai-apiserver/test-requirements.txt b/examples/industrial-ai-apiserver/test-requirements.txt old mode 100644 new mode 100755 index dcb19c6f2a4560264933f6b3baa83a2c043ebc1e..7265755ff79565a4bb7619f28570640f5b57637a --- a/examples/industrial-ai-apiserver/test-requirements.txt +++ b/examples/industrial-ai-apiserver/test-requirements.txt @@ -1,16 +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 +# 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/examples/industrial-ai-apiserver/tox.ini b/examples/industrial-ai-apiserver/tox.ini old mode 100644 new mode 100755 index deff8bc2e1b66a31194c7130b85e633ff5b40ab9..f8200ff690753af0b51725bd11cb6dda43fdcd73 --- a/examples/industrial-ai-apiserver/tox.ini +++ b/examples/industrial-ai-apiserver/tox.ini @@ -1,53 +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 +[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/examples/myapp-doc.py b/examples/myapp-doc.py old mode 100644 new mode 100755 index f48ef1b760c110abcb3a9ba9b47e7deeb148eb36..878912245ff0287edc56598e79be01655d3f8b22 --- a/examples/myapp-doc.py +++ b/examples/myapp-doc.py @@ -1,8 +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')) +#!/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/examples/myapp.py b/examples/myapp.py old mode 100644 new mode 100755 index 88dcc0beeca489ffc3b306012b73a82e5fc0e4d3..fcaa3a36c6119d5f7f1c5f8878242d75b52c203c --- a/examples/myapp.py +++ b/examples/myapp.py @@ -1,47 +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() +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/pecan_swagger/__init__.py b/pecan_swagger/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/pecan_swagger/decorators.py b/pecan_swagger/decorators.py deleted file mode 100644 index 5b8ef4aefaa1919dbeca2d4e6f9715f41b14c6ed..0000000000000000000000000000000000000000 --- a/pecan_swagger/decorators.py +++ /dev/null @@ -1,38 +0,0 @@ -import pecan_swagger.g as g - -""" -decorators module - -these decorators are meant to be used by developers who wish to markup -their pecan applications to produce swagger. -""" - - -def path(endpoint, name, parent=None): - """ - path decorator - - this decorator should be used on pecan controllers to instruct how - they map to routes. - - :param endpoint: the root uri for this controller. - :param name: the name of this path. - :param parent: an optional path name to indicate a parent/child - relationship between this path and another. - """ - def decorator(c): - if hasattr(c, '__swag'): - raise Exception('{} already has swag'.format(c.__name__)) - c.__swag = dict(endpoint=endpoint, name=name, parent=parent) - g.add_path(c) - return c - return decorator - - -def method(method): - def decorator(m): - if hasattr(m, '__swag'): - raise Exception('{} already has swag'.format(m.__name__)) - m.__swag = dict(method=method) - return m - return decorator diff --git a/pecan_swagger/g.py b/pecan_swagger/g.py deleted file mode 100644 index 383c2c6fda52f76e98717f62d5b99155dc7fc12f..0000000000000000000000000000000000000000 --- a/pecan_swagger/g.py +++ /dev/null @@ -1,487 +0,0 @@ -import copy -import inspect - -from pecan import util as p_u -import wsme.types as wtypes -import six -import weakref - - -""" -global hierarchy module - -this module is at the heart of the swagger conversion utility. it -contains a global "hierarchy" object which will provide a single point -to assemble an application's swagger output. - -there are also several helper functions to assist in building the -hierarchy of controllers, routes, and methods. -""" - -_hierarchy = {} - -_http_methods = {'get': '', 'post': '', 'put': '', - 'patch': '', 'delete': '', - 'head': '', 'trace': ''} - -_all_wsme_types = wtypes.native_types + ( - wtypes.ArrayType, wtypes.DictType, wtypes.BinaryType, - wtypes.IntegerType, wtypes.StringType, wtypes.IPv4AddressType, - wtypes.IPv6AddressType, wtypes.UuidType, wtypes.Enum, wtypes.UnsetType) - -_definitions = {} - - -def add_path(c): - """adds a named controller to the hierarchy.""" - if _hierarchy.get(c.__swag['name']): - raise Exception( - 'name {} already exists in hierarchy'.format(c.__swag['name'])) - _hierarchy[c.__swag['name']] = c - - -def build_path(swaginfo): - """return the route path for a swag metadata item.""" - if swaginfo.get('parent') is not None: - return path_join(build_path(get_swag(swaginfo.get('parent'))), - swaginfo.get('endpoint')) - return swaginfo.get('endpoint') - - -def get_controller_paths(controllers, wsme_defs): - """ - get a list of paths with methods - - returns a list of tuples (controller path, methods). - """ - def get_methods_for_generic(name): - methods = [] - generic_handlers = lc.get(name).get('generic_handlers', {}) - for method, func in generic_handlers.items(): - if method == 'DEFAULT': - methods.append('get') - else: - methods.append(method.lower()) - # TODO drill down through decorators to get true function name - truename = getrealname(func) - del lc[truename] - return methods - - def append_methods_for_specific(name, tpl): - paths.append(tpl) - del lc[name] - - lc = copy.deepcopy(controllers) - paths = [] - # TODO incorporate method decorator, removing functions marked - if lc.get('index'): - for method in get_methods_for_generic('index'): - paths.append(('', (method, {}))) - - # for REST controller - for method, path in _http_methods.items(): - if lc.get(method): - spec = wsme_defs.get(method, {}) # add wsme definitions - append_methods_for_specific(method, (path, (method, spec))) - - if lc.get('get_all'): - # add wsme definitions - spec = wsme_defs.get('get_all') # add wsme definitions - append_methods_for_specific('get_all', ('', ('get', spec))) - if lc.get('get_one'): - # add wsme definitions - spec = wsme_defs.get('get_one') # add wsme definitions - append_methods_for_specific('get_one', ('', ('get', spec))) - - if lc.get('_default'): - append_methods_for_specific('_default', ('', ('*', {}))) - if lc.get('_route'): - append_methods_for_specific('_route', ('', ('*', {}))) - if lc.get('_lookup'): - del lc['_lookup'] - - if lc.get('_custom_actions'): - for ca, ca_method in lc['_custom_actions'].items(): - spec = wsme_defs.get(ca) # add wsme definitions - for m in ca_method: - paths.append((ca, (m.lower(), spec))) - del lc['_custom_actions'] - - generic_controllers = [c for c in lc if lc[c].get('generic')] - for controller in generic_controllers: - for method in get_methods_for_generic(controller): - paths.append((controller, (method, {}))) - for controller in lc: - if controller not in [path[0] for path in paths]: - paths.append((controller, ('get', {}))) - return paths - - -def get_wsme_defs(name): - - def datatype_to_type_and_format(datatype): - tf = {} - if datatype == 'uuid' or datatype == 'ipv4address' \ - or datatype == 'ipv6address' or datatype == 'date' \ - or datatype == 'time': - tf['type'] = 'string' - tf['format'] = datatype - elif datatype == 'datetime': - tf['type'] = 'string' - tf['format'] = 'date-time' - elif datatype == 'binary' or datatype == 'bytes': - # base64 encoded characters - tf['type'] = 'string' - tf['format'] = 'byte' - elif datatype == 'array' or datatype == 'boolean' \ - or datatype == 'integer' or datatype == 'string': - # no format - tf['type'] = datatype - elif datatype == 'float': - # number - tf['type'] = 'number' - tf['format'] = datatype - elif datatype == 'unicode' or datatype == 'str' \ - or datatype == 'text': - # primitive, no format - tf['type'] = 'string' - elif datatype == 'int' or datatype == 'decimal': - # primitive, no format - tf['type'] = 'integer' - elif datatype == 'bool': - # primitive, no format - tf['type'] = 'boolean' - elif datatype == 'enum': - tf['type'] = 'enum' - elif datatype == 'unset' or datatype == 'none': - tf['type'] = None - elif datatype == 'dict': - tf['type'] = 'object' - else: - tf['type'] = 'object' - return tf - - def class_to_name_str(c): - return (('%s' % c).replace('<', '').replace('>', '') - .replace('class ', '').replace('\'', '') - .split(' ', 1)[0].rsplit('.', 1)[-1]) - - def conv_class_name_to_type_str(cn): - type_str = '' - if cn.endswith('Type'): - type_str = cn[:-4] - elif cn == 'str': - type_str = 'string' - else: - type_str = cn - type_str = type_str.lower() - return type_str - - def get_type_str(obj, src_dict=None): - type_str = '' - if hasattr(obj, '__name__'): - type_str = obj.__name__ - else: - type_str = obj.__class__.__name__ - type_str = conv_class_name_to_type_str(type_str) - - tf = datatype_to_type_and_format(type_str) - - if hasattr(obj, 'basetype') and \ - (obj.__class__ not in _all_wsme_types or type_str == 'enum'): - # UserType or Enum - tf['type'] = get_type_str(obj.basetype) - - if isinstance(src_dict, dict): - # if dict is in args, set 'type' and 'format' into the dict and - # return - src_dict.update({'type': tf['type']}) - if 'format' in tf: - src_dict.update({'format': tf['format']}) - - # get datatype options. ex.) min_length, minimum, .. - for k, v in inspect.getmembers(obj): - if ((k == 'minimum' or k == 'maxmum' - or k == 'min_length' or k == 'max_length') - and v is not None): - src_dict[to_swag_key(k)] = v - elif k == 'pattern' and v is not None: - src_dict[to_swag_key(k)] = v.pattern - # TODO(shu-mutou): this should be removed for production. - # else: - # src_dict[to_swag_key(k)] = v - - if type_str == 'enum': - # EnumType use 'set' that doesn't have sequence. - # So use 'sorted' for keeping static output. - src_dict['enum'] = sorted([v for v in obj.values]) - - # return 'type' only - return tf['type'] - - def to_swag_key(key): - keys = { - 'doc': 'description', - 'arguments': 'parameters', - 'return_type': 'schema', - 'datatype': 'type', - 'mandatory': 'required', - 'sample': 'examples', - 'readonly': 'readOnly', - 'min_length': 'minLength', - 'max_length': 'maxLength', - } - if key in keys: - return keys[key] - else: - return key - - def get_wm_item_prop(item, isparams=False): - # Add prop into 'properties' and 'required' in 'Items Object' - # 'Items Object' can be part of 'Schema Object' or 'Items Object', - # and can use recursively - prop_dict = {} - # TODO(shu-mutou): this should be removed for production. - # prop_dict['obj'] = inspect.getmembers(item) - - _item = item - if wtypes.iscomplex(item): - _item = weakref.ref(item) - - for a, i in inspect.getmembers(_item): - if a == 'datatype': - datatype = get_type_str(i, prop_dict) - if datatype == 'array': - # if array, do recursively - prop_dict['items'] = inspect_wm_schema(i.item_type) - elif datatype == 'object': - # if obuject, do recursively - prop_dict['items'] = inspect_wm_schema(i) - elif a == 'default' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'name' and isparams: - prop_dict[to_swag_key(a)] = i - elif a == 'mandatory' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'readonly' and i: - prop_dict[to_swag_key(a)] = i - elif a == 'doc' and i is not None: - prop_dict[to_swag_key(a)] = i - - if isparams and prop_dict['type'] in ['object', 'array']: - prop_dict['schema'] = {'items': prop_dict['items'], - 'type': prop_dict['type']} - del prop_dict['type'] - del prop_dict['items'] - return prop_dict - - def get_wsattr_and_wsproperty(obj): - ws_dict = {} - for key, value in inspect.getmembers(obj): - if (key[0] != '_' and - (value.__class__.__name__ == 'wsattr' - or value.__class__.__name__ == 'wsproperty')): - ws_dict[key] = value - return ws_dict - - def inspect_wm_schema(schema_obj, isparams=False): - schema_dict = {} - # TODO(shu-mutou): this should be removed for production. - # schema_dict['obj'] = get_wsattr_and_wsproperty(schema_obj) - ws_len = len(get_wsattr_and_wsproperty(schema_obj)) - model_name = class_to_name_str(schema_obj) - - for key, obj in inspect.getmembers(schema_obj): - if (key[0] != '_' and - (obj.__class__.__name__ == 'wsattr' - or obj.__class__.__name__ == 'wsproperty')): - # TODO(shu-mutou): this should be removed for production. - # _definitions[model_name][to_swag_key(key)] = \ - # {'obj': inspect.getmembers(obj)} - - if ws_len == 1: - # single schema - schema_dict = get_wm_item_prop(obj, isparams) - else: - # multi property schema - schema_dict.update({'type': 'object'}) - # properties - if 'items' not in schema_dict: - schema_dict['items'] = {'properties': {}} - prop = {key: get_wm_item_prop(obj, isparams)} - # required as array of string - if 'required' in prop[key] and prop[key]['required'] \ - and isinstance(prop[key]['required'], bool): - if 'required' not in schema_dict: - schema_dict['required'] = [] - schema_dict['required'].append(key) - del prop[key]['required'] - schema_dict['items']['properties'].update(prop) - - if schema_obj not in _all_wsme_types: - if model_name not in _definitions: - _definitions[model_name] = schema_dict - schema_dict = {'$ref': "#/definitions/%s" % model_name} - - return schema_dict - - def get_wm_def(o): - wsme = {'description': ''} - for w, d in inspect.getmembers(o): - if w == 'arguments': - wsme[to_swag_key(w)] = [] - for arg in d: - # set each 'Parameter Object', it can include - # 'Items Object' recursively - item_dict = get_wm_item_prop(arg, True) - # TODO: MUST be set one of - # 'body|query|path|header|formData' - item_dict['in'] = 'query' - wsme[to_swag_key(w)].append(item_dict) - # 'body' should be set due to existing 'body' option - # in WSME expose - if o.body_type is not None: - # if body is set, last arg is it. - wsme[to_swag_key(w)][-1]['in'] = 'body' - elif w == 'doc' and d: - wsme[to_swag_key(w)] = d - elif w == 'return_type': - wsme['responses'] = {'status': {'description': ''}} - if d: - if d in _all_wsme_types: - # d is set single WSME type class or implicit type - wsme['responses']['status'][to_swag_key(w)] = ( - datatype_to_type_and_format( - conv_class_name_to_type_str( - class_to_name_str(d)))) - else: - # d is model class - wsme['responses']['status'][to_swag_key(w)] = ( - inspect_wm_schema(d, False)) - doc = inspect.getdoc(d) - if doc is not None: - wsme['responses']['status']['description'] = doc - elif w == 'status_code': - wsme['responses'][d] = wsme['responses']['status'] - del wsme['responses']['status'] - # TODO(shu-mutou): this should be removed for production. - # elif w == 'body_type': - # wsme[to_swag_key(w)] = get_type_str(d) - # elif w == 'extra_options' or w == 'ignore_extra_args' \ - # or w == 'name' or w == 'pass_request': - # wsme[to_swag_key(w)] = d - # else: - # wsme[to_swag_key(w)] = d - return wsme - - c = _hierarchy[name] - wsme_defs = {} - for k, v in inspect.getmembers(c): - if p_u.iscontroller(v): - for m, o in inspect.getmembers(v): - if m == "_wsme_definition": - wsme_defs[k] = get_wm_def(o) - - # TODO(shu-mutou): this should be removed for production. - # output wsme info into files by each controller for dev - # import pprint - # with open(name + '.txt', 'w') as fout: - # pprint.pprint(wsme_defs, stream=fout, indent=2) - - return wsme_defs - - -def get_controllers(name): - """ - get all the controllers associated with a path - - returns a dictionary of controllers indexed by their names. - """ - c = _hierarchy[name] - controllers = {k: p_u._cfg(v) - for k, v in c.__dict__.items() if p_u.iscontroller(v)} - cacts = {k: v for k, v in c.__dict__.items() if k == '_custom_actions'} - if len(cacts): - controllers.update(cacts) - return controllers - - -def get_paths(): - """ - return all the registered paths - - loops through the hierarchy and retuns a list of tuples containing the - paths and their methods. - - :returns: [(path, methods), ...] - """ - pathlist = [] - for name in _hierarchy: - fullpath = build_path(get_swag(name)) - controllers = get_controllers(name) - wsme_defs = get_wsme_defs(name) - paths = get_controller_paths(controllers, wsme_defs) - for path in paths: - ptuple = (path_join(fullpath, path[0]), path[1]) - pathlist.append(ptuple) - return pathlist - - -def get_swag(name): - """return the swag metadata from an named controller.""" - return _hierarchy.get(name).__swag - - -def getrealname(method): - """attempt to get a method's real name.""" - argspec = inspect.getargspec(method) - args = argspec[0] - if args and args[0] == 'self': - return method.__name__ - if hasattr(method, '__func__'): - method = method.__func__ - - func_closure = six.get_function_closure(method) - - # NOTE(sileht): if the closure is None we cannot look deeper, - # so return actual argspec, this occurs when the method - # is static for example. - if func_closure is None: - return method.__name__ - - closure = next( - ( - c for c in func_closure if six.callable(c.cell_contents) - ), - None - ) - method = closure.cell_contents - return getrealname(method) - - -def methods_get(name): - """get all the methods for a named controller.""" - c = _hierarchy[name] - mlist = [] - if hasattr(c, 'index') and p_u.iscontroller(c.index): - cfg = p_u._cfg(c.index) - if cfg.get('generic_handlers').get('DEFAULT'): - mlist.append('get') - mlist += cfg.get('allowed_methods') - for i in c.__dict__: - ii = getattr(c, i) - if hasattr(ii, '__swag'): - m = ii.__swag.get('method') - if m is not None: - mlist.append(m) - return mlist - - -def path_join(part1, part2): - """join two url paths.""" - if len(part2) == 0: - return part1 - sep = '/' - if part1[-1] == sep: - sep = '' - return part1 + sep + part2 diff --git a/pecan_swagger/utils.py b/pecan_swagger/utils.py deleted file mode 100644 index aa3d3b6155a6322005791e833abb278cd6ed2a01..0000000000000000000000000000000000000000 --- a/pecan_swagger/utils.py +++ /dev/null @@ -1,64 +0,0 @@ -from pecan_swagger import g as g - - -""" -utility function module - -this module contains the utility functions to assemble the swagger -dictionary object. they can be consumed by end-user applications to -build up swagger objects for applications. - -functions: -swagger_build -- build a full swagger dictionary -""" - - -def swagger_build(title, version): - swag = dict() - swag['swagger'] = '2.0' - swag['info'] = dict(title=title, version=version) - swag['consumes'] = [] - swag['produces'] = [] - swag['paths'] = {} - for p in g.get_paths(): - if p[0] not in swag['paths']: - swag['paths'][p[0]] = _tuple_to_dict(p[1]) - elif len(p[1]) > 0: - swag['paths'][p[0]].update(_tuple_to_dict(p[1])) - swag['definitions'] = g._definitions - return swag - - -def _tuple_to_dict(tpl): - """Convert tuple to dictionary - - each tuple must have key and value. - This function arrows taple including lists. - - ex.) acceptable taple - OK: ('/', ('get',(('desc',{}),('res',{}),('params',['id','name'])))) - NG: ('/', ('get',('desc','res',('params',['id','name'])))) - """ - if isinstance(tpl, list): - d = [] - for e in tpl: - d.append(_tuple_to_dict(e)) - elif isinstance(tpl, tuple): - d = {} - if isinstance(tpl[0], tuple): - # tuple has some child tuple - for e in tpl: - d[e[0]] = _tuple_to_dict(e[1]) - elif isinstance(tpl[0], list): - # list member should be processed recursively - d = _tuple_to_dict(tpl[0]) - else: - if len(tpl) == 2: - # single tuple node - d[tpl[0]] = _tuple_to_dict(tpl[1]) - else: - raise Exception(tpl) - else: - # value or dict - d = tpl - return d