add swagger (#1)

Co-authored-by: Michael Grote <michael.grote@posteo.de>
Reviewed-on: mg/python-api-server#1
This commit is contained in:
Michael Grote 2023-04-25 20:52:45 +02:00
parent ef96c7167a
commit 13a3ab0076
4 changed files with 111 additions and 107 deletions

111
README.md
View file

@ -2,7 +2,6 @@
a small flask-application for storing and downloading stuff like small binaries a small flask-application for storing and downloading stuff like small binaries
## Variables ## Variables
- ``MAX_CONTENT_LENGTH``: maximal Filesize in MB; defaults to 5MB - ``MAX_CONTENT_LENGTH``: maximal Filesize in MB; defaults to 5MB
@ -11,94 +10,28 @@ a small flask-application for storing and downloading stuff like small binaries
## Example Docker-Compose ## Example Docker-Compose
see [docker-compose.yml](./docker-compose.yml) ```yaml
version: '3'
services:
python-api-server:
container_name: httpd-api
image: quotengrote/python-api-server:v2
ports:
- "5040:5000"
volumes:
- uploads:/uploads
environment:
# FLASK_DEBUG: 1 # for debugging
# FLASK_APP: app # for debugging
MAX_CONTENT_LENGTH: 10
UPLOAD_DIRECTORY: /uploads
AUTH_TOKEN: myuploadtoken
volumes:
uploads:
```
## API-Endpoints ## API-Endpoints
### /list - see [Flasgger](https://github.com/flasgger/flasgger): ``http://<host>:5040/apidocs/``
#### input
```bash
curl -H "token: myuploadtoken" http://docker10.host.lan:5040/list | jq
```
#### output
```bash
{
"files": [
{
"last_modified": "2023-04-13 11:43:51",
"name": "file1",
"size": 1034
},
{
"last_modified": "2023-04-13 11:53:59",
"name": "file2",
"size": 5
},
{
"last_modified": "2023-04-13 12:41:18",
"name": "file3",
"size": 3478
}
]
}
```
### /upload
If a existing file has the same name as the newly uploaded file, it will be overwritten.
#### input
```bash
curl -X POST -H "token: myuploadtoken" -F "file=@tests/file" http://docker10.host.lan:5040/upload | jq
```
#### output
```bash
{
"success": "File 'file' successfully uploaded"
}
```
### /download
#### input
```bash
wget http://docker10.host.lan:5040/download/file
```
### /delete
#### input
```bash
curl -X DELETE -H "token: myuploadtoken" http://docker10.host.lan:5040/delete/file | jq
```
#### output
```bash
{
"success": "File 'file' successfully deleted"
}
```
### /health
#### input
```bash
curl http://docker10.host.lan:5040/health
```
#### output
```bash
OK
```

88
app.py
View file

@ -3,8 +3,10 @@ import re
import uuid import uuid
from flask import Flask, request, jsonify, send_from_directory from flask import Flask, request, jsonify, send_from_directory
import datetime import datetime
from flasgger import Swagger, swag_from
app = Flask(__name__) app = Flask(__name__)
swagger = Swagger(app)
app.config['UPLOAD_DIRECTORY'] = os.environ.get('UPLOAD_DIRECTORY', '/uploads') app.config['UPLOAD_DIRECTORY'] = os.environ.get('UPLOAD_DIRECTORY', '/uploads')
app.config['MAX_CONTENT_LENGTH'] = int(os.environ.get('MAX_CONTENT_LENGTH', '5')) * 1024 * 1024 # in MB app.config['MAX_CONTENT_LENGTH'] = int(os.environ.get('MAX_CONTENT_LENGTH', '5')) * 1024 * 1024 # in MB
@ -17,10 +19,41 @@ def is_valid_filename(filename):
@app.route('/health', methods=['GET']) @app.route('/health', methods=['GET'])
def health_check(): def health_check():
"""
Endpoint for health check.
---
responses:
200:
description: OK
"""
return 'OK' return 'OK'
@app.route('/upload', methods=['POST']) @app.route('/upload', methods=['POST'])
def upload_file(): def upload_file():
"""
Endpoint for uploading files.
Filename can only contain alphanumeric characters, hyphens, underscores, and periods.
If the filename is the same as an existing file, this file will be overwritten.
---
parameters:
- name: file
in: formData
type: file
required: true
description: The file to upload.
- name: token
in: header
type: string
required: true
description: Authentication token.
responses:
200:
description: File uploaded successfully.
400:
description: Bad request.
401:
description: Unauthorized.
"""
if 'file' not in request.files: if 'file' not in request.files:
return jsonify({'error': 'No file part in the request'}), 400 return jsonify({'error': 'No file part in the request'}), 400
@ -43,6 +76,21 @@ def upload_file():
@app.route('/download/<filename>', methods=['GET']) @app.route('/download/<filename>', methods=['GET'])
def download_file(filename): def download_file(filename):
"""
Endpoint for downloading files.
---
parameters:
- name: filename
in: path
type: string
required: true
description: The name of the file to download.
responses:
200:
description: File downloaded successfully.
404:
description: File not found.
"""
try: try:
return send_from_directory(app.config['UPLOAD_DIRECTORY'], filename) return send_from_directory(app.config['UPLOAD_DIRECTORY'], filename)
except FileNotFoundError: except FileNotFoundError:
@ -50,6 +98,28 @@ def download_file(filename):
@app.route('/delete/<filename>', methods=['DELETE']) @app.route('/delete/<filename>', methods=['DELETE'])
def delete_file(filename): def delete_file(filename):
"""
Endpoint for deleting files.
---
parameters:
- name: filename
in: path
type: string
required: true
description: The name of the file to delete.
- name: token
in: header
type: string
required: true
description: Authentication token.
responses:
200:
description: File deleted successfully.
401:
description: Unauthorized.
404:
description: File not found.
"""
if 'token' not in request.headers: if 'token' not in request.headers:
return jsonify({'error': 'No token supplied'}), 401 return jsonify({'error': 'No token supplied'}), 401
@ -63,8 +133,26 @@ def delete_file(filename):
os.remove(file_path) os.remove(file_path)
return jsonify({'success': 'File \'{}\' successfully deleted'.format(filename)}) return jsonify({'success': 'File \'{}\' successfully deleted'.format(filename)})
@app.route('/list', methods=['GET']) @app.route('/list', methods=['GET'])
def list_files(): def list_files():
"""
Endpoint for listing files.
---
parameters:
- name: token
in: header
type: string
required: true
description: Authentication token.
responses:
200:
description: Listed files successfully.
400:
description: Bad request.
401:
description: Unauthorized.
"""
if 'token' not in request.headers: if 'token' not in request.headers:
return jsonify({'error': 'No token supplied'}), 401 return jsonify({'error': 'No token supplied'}), 401

View file

@ -1,18 +0,0 @@
version: '3'
services:
python-api-server:
container_name: httpd-api
image: quotengrote/python-api-server:latest
ports:
- "5040:5000"
volumes:
- uploads:/uploads
environment:
# FLASK_DEBUG: 1 # for debugging
# FLASK_APP: app # for debugging
MAX_CONTENT_LENGTH: 10
UPLOAD_DIRECTORY: /uploads
AUTH_TOKEN: myuploadtoken
volumes:
uploads:

View file

@ -2,3 +2,4 @@ Flask==2.0.1
Flask-Cors==3.0.10 Flask-Cors==3.0.10
gunicorn gunicorn
requests requests
flasgger