Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PAN-2158: Adding OpenAPI docs generation #143

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
PANTOS_SERVICE_NODE_VERSION := $(shell command -v poetry >/dev/null 2>&1 && poetry version -s || echo "0.0.0")
PANTOS_SERVICE_NODE_SSH_HOST ?= bdev-service-node
PYTHON_FILES_WITHOUT_TESTS := pantos/servicenode linux/scripts/start-web.py
PYTHON_FILES_WITHOUT_TESTS := pantos/servicenode linux/scripts/start-web.py openapi.py
PYTHON_FILES := $(PYTHON_FILES_WITHOUT_TESTS) tests
STACK_BASE_NAME=stack-service-node
INSTANCE_COUNT ?= 1
DEV_MODE ?= false
SHELL := $(shell which bash)
OPENAPI_FILE_LOCATION ?= ./docs/openapi.json

.PHONY: check-version
check-version:
Expand Down Expand Up @@ -83,6 +84,10 @@ coverage-all:
.PHONY: tar
tar: dist/pantos_service_node-$(PANTOS_SERVICE_NODE_VERSION).tar.gz

.PHONY: openapi-docs
openapi-docs:
poetry run python3 -m openapi $(OPENAPI_FILE_LOCATION)

dist/pantos_service_node-$(PANTOS_SERVICE_NODE_VERSION).tar.gz: pantos/ service-node-config.yml service-node-config.env bids.yml alembic.ini pantos-service-node.sh pantos-service-node-worker.sh
cp service-node-config.yml pantos/service-node-config.yml
cp service-node-config.env pantos/service-node-config.env
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,21 @@ Run the following command from the repository's root directory:
make code
```

### 3.2 Local development environment
### 3.2 OpenAPI

If you want to generate the OpenAPI documentation, you can run the following command:

```bash
make openapi-docs
```
which will generate a `openapi.json` file in the `docs` directory.
If you want to specify a different path for the output file, you can do so by running:

```bash
make openapi-docs OUTPUT_FILE=<path>/<filename.json>
```

### 3.3 Local development environment

#### PostgreSQL

Expand Down
42 changes: 42 additions & 0 deletions openapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import json
import os
import sys
from pathlib import Path

from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flasgger import APISpec # type: ignore
from flasgger import Swagger

from pantos.servicenode.restapi import _BidSchema
from pantos.servicenode.restapi import _BidsSchema
from pantos.servicenode.restapi import _TransferSchema
from pantos.servicenode.restapi import _TransferStatusSchema
from pantos.servicenode.restapi import flask_app

DOCS_PATH = "docs/openapi.json"

if len(sys.argv) > 1:
DOCS_PATH = sys.argv[1]

plugins = [FlaskPlugin(), MarshmallowPlugin()]
spec = APISpec("Pantos Service Node APISpec", '1.0', "3.0.2", plugins=plugins)

template = spec.to_flasgger(
flask_app, definitions=[
_BidSchema, _BidsSchema, _TransferSchema, _TransferStatusSchema
])

swagger = Swagger(flask_app, template=template, parse=True)

with flask_app.test_request_context():
data = swagger.get_apispecs()
data.pop('definitions')
data.pop('swagger')
data['servers'] = [{'url': 'https://sn1.testnet.pantos.io'}]

if not (Path.cwd() / DOCS_PATH).exists():
os.makedirs(os.path.dirname(DOCS_PATH), exist_ok=True)

with open(DOCS_PATH, "w") as f:
f.write(json.dumps(data, indent=4))
125 changes: 125 additions & 0 deletions pantos/servicenode/restapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,45 @@ class _Transfer(flask_restful.Resource):

"""
def post(self) -> flask.Response:
"""
Endpoint for submitting a token transfer request.
---
requestBody:
description: Transfer request
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/_Transfer"
responses:
200:
description: Transfer request accepted
content:
application/json:
schema:
type: object
example:
task_id: '123e4567-e89b-12d3-a456-426614174000'
406:
description: Transfer request no accepted
content:
application/json:
schema:
type: array
items:
type: string
example: "[bid has been rejected by service node: \
'bid not accepted']"
409:
description: Sender nonce from transfer request is not unique
content:
application/json:
schema:
type: string
example: sender nonce 1337 is not unique
500:
description: Internal server error
"""
try:
time_received = time.time()
arguments = flask_restful.request.json
Expand Down Expand Up @@ -244,6 +283,53 @@ class _TransferStatus(flask_restful.Resource):

"""
def get(self, task_id: str) -> flask.Response:
"""
Endpoint that returns the status of a transfer.
---
parameters:
- in: path
name: task_id
schema:
$ref: '#/components/schemas/_TransferStatus'
required: true
description: Id of a transfer submitted to the service node
responses:
200:
description: Object containing the status of a transfer with
the given task ID
content:
application/json:
schema:
type: object
example:
task_id: '123e4567-e89b-12d3-a456-426614174000'
source_blockchain_id: 1
destination_blockchain_id: 2
sender_address: \
'0x1234567890123456789012345678901234567890'
recipient_address: \
'0x1234567890123456789012345678901234567890'
source_token_address: \
'0x1234567890123456789012345678901234567890'
destination_token_address: \
'0x1234567890123456789012345678901234567890'
amount: 100
fee: 1
status: 'pending'
transfer_id: \
'0x1234567890123456789012345678901234567890'
transaction_id: \
'0x1234567890123456789012345678901234567890'
404:
description: 'not found'
content:
application/json:
schema:
type: string
example: {"message": "task ID 123 is unknown"}
500:
description: 'internal server error'
"""
try:
task_id_uuid = _TransferStatusSchema().load({'task_id': task_id})
_logger.info(f'new transfer status request: {task_id}')
Expand Down Expand Up @@ -289,6 +375,45 @@ class _Bids(flask_restful.Resource):

"""
def get(self) -> flask.Response:
"""
Endpoint that returns a list of bids for a given source and \
destination blockchain.
---
parameters:
- in: query
name: source_blockchain
schema:
$ref: '#/components/schemas/_Bids/properties/source_blockchain'
required: true
description: Numeric ID of the supported Blockchain ID
- in: query
name: destination_blockchain
schema:
$ref: \
'#/components/schemas/_Bids/properties/destination_blockchain'
required: true
description: Numeric ID of the supported Blockchain ID
responses:
200:
description: List of bids for a given source and \
destination blockchain
content:
application/json:
schema:
$ref: '#/components/schemas/_Bid'
400:
description: 'bad request'
content:
application/json:
schema:
type: string
example: {"message": {"source_blockchain": \
["Missing data for required field."], \
"destination_blockchain": \
["Missing data for required field."]}}
500:
description: 'internal server error'
"""
try:
query_arguments = flask_restful.request.args
bids_parameter = _BidsSchema().load(query_arguments)
Expand Down
72 changes: 70 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ yapf = "0.40.2"
[tool.poetry.group.dev.dependencies]
pre-commit = "4.0.1"


[tool.poetry.group.docs.dependencies]
apispec = "^6.7.1"
apispec-webframeworks = "^1.2.0"
flasgger = "^0.9.7.1"

[tool.poetry.dependencies]
python = "^3.10"
pantos-common = "4.0.1"
Expand Down