add swagger (#1)
Co-authored-by: Michael Grote <michael.grote@posteo.de> Reviewed-on: mg/python-api-server#1
This commit is contained in:
parent
ef96c7167a
commit
13a3ab0076
4 changed files with 111 additions and 107 deletions
111
README.md
111
README.md
|
@ -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
88
app.py
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
|
|
@ -2,3 +2,4 @@ Flask==2.0.1
|
||||||
Flask-Cors==3.0.10
|
Flask-Cors==3.0.10
|
||||||
gunicorn
|
gunicorn
|
||||||
requests
|
requests
|
||||||
|
flasgger
|
||||||
|
|
Reference in a new issue