Skip to content

Commit

Permalink
Merge pull request #143 from jacekv/feature/PAN-2158-api-docs
Browse files Browse the repository at this point in the history
PAN-2158: Adding OpenAPI docs generation
  • Loading branch information
jacekv authored Nov 13, 2024
2 parents 2f2a710 + 84b9c9a commit 0f6b813
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 4 deletions.
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

0 comments on commit 0f6b813

Please sign in to comment.