Skip to content
Snippets Groups Projects
Commit f61569ce authored by geant-release-service's avatar geant-release-service
Browse files

Finished release 0.1.

parents 0ebf2349 7db6589a
No related branches found
No related tags found
No related merge requests found
Showing
with 87969 additions and 0 deletions
*.egg-info
__pycache__
coverage.xml
.coverage
htmlcov
.tox
dist
.idea
node_modules
docs/build
# Changelog
All notable changes to this project will be documented in this file.
## [0.1] - 2022-11-07
- Initial skeleton - First Release
- COMP-2 Prototype Data only Service Matrix
- COMP-31 Baseline React routing structure
recursive-include compendium_v2/static *
include compendium_v2/datasources/dummy-service-matrix.json *
include compendium_v2/templates/index.html *
# Skeleton Web App
## Installation Process
```bash
$ git clone https://gitlab.geant.net/live-projects/compendium-v2.git
```
```bash
$ python3 -m venv compendium-v2
$ .compendium-v2/bin/activate
$ pip install tox
$ pip install -r requirements.txt
$ cd compendium-v2
$ tox -e py39
```
## Overview
This module implements a skeleton Flask-based webservice
and in-browser React front-end.
The webservice communicates with the front end over HTTP.
Responses to valid requests are returned as JSON messages.
The server will therefore return an error unless
`application/json` is in the `Accept` request header field.
HTTP communication and JSON grammar details are
beyond the scope of this document.
Please refer to [RFC 2616](https://tools.ietf.org/html/rfc2616)
and www.json.org for more details.
## Configuration
This app allows specification of a few
example configuration parameters. These
parameters should stored in a file formatted
similarly to `config.json.example`, and the name
of this file should be stored in the environment
variable `SETTINGS_FILENAME` when running the service.
## Building the web application
The initial repository doesn't contain the required web application.
For instructions on building this see `webapp/README.md`.
## Running this module
This module has been tested in the following execution environments:
- As an embedded Flask application.
For example, the application could be launched as follows:
```bash
$ export FLASK_APP=compendium_v2.app
$ export SETTINGS_FILENAME=config-example.json
$ flask run
```
See https://flask.palletsprojects.com/en/2.1.x/deploying/
for best practices about running in production environments.
### resources
Any non-empty responses are JSON formatted messages.
#### /data/version
* /version
The response will be an object
containing the module and protocol versions of the
running server and will be formatted as follows:
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"api": {
"type": "string",
"pattern": r'\d+\.\d+'
},
"module": {
"type": "string",
"pattern": r'\d+\.\d+'
}
},
"required": ["api", "module"],
"additionalProperties": False
}
```
#### /test/test1
The response will be some json data, as an example ...
"""
automatically invoked app factory
"""
import logging
import os
from flask import Flask
from flask_cors import CORS # for debugging
from compendium_v2 import environment
from compendium_v2 import config
def create_app():
"""
overrides default settings with those found
in the file read from env var SETTINGS_FILENAME
:return: a new flask app instance
"""
assert 'SETTINGS_FILENAME' in os.environ
with open(os.environ['SETTINGS_FILENAME']) as f:
app_config = config.load(f)
app = Flask(__name__)
CORS(app)
app.secret_key = 'super secret session key'
app.config['CONFIG_PARAMS'] = app_config
from compendium_v2.routes import default
app.register_blueprint(default.routes, url_prefix='/')
from compendium_v2.routes import api
app.register_blueprint(api.routes, url_prefix='/api')
from compendium_v2.routes import service_matrix
app.register_blueprint(service_matrix.routes, url_prefix='/service-matrix')
logging.info('Flask app initialized')
environment.setup_logging()
return app
"""
default app creation
"""
import compendium_v2
from compendium_v2 import environment
environment.setup_logging()
app = compendium_v2.create_app()
if __name__ == "__main__":
app.run(host="::", port="33333")
{
"str-param": "some string",
"int-param": -1234
}
import json
import jsonschema
CONFIG_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'str-param': {'type': 'string'},
'int-param': {'type': 'integer'}
},
'required': ['str-param', 'int-param'],
'additionalProperties': False
}
def load(f):
"""
loads, validates and returns configuration parameters
:param f: file-like object that produces the config file
:return:
"""
config = json.loads(f.read())
jsonschema.validate(config, CONFIG_SCHEMA)
return config
This diff is collapsed.
import json
import logging.config
import os
LOGGING_DEFAULT_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(asctime)s - %(name)s '
'(%(lineno)d) - %(levelname)s - %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'simple',
'stream': 'ext://sys.stdout'
}
},
'loggers': {
'compendium_v2': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False
}
},
'root': {
'level': 'WARNING',
'handlers': ['console']
}
}
def setup_logging():
"""
set up logging using the configured filename
if LOGGING_CONFIG is defined in the environment, use this for
the filename, otherwise use LOGGING_DEFAULT_CONFIG
"""
logging_config = LOGGING_DEFAULT_CONFIG
if 'LOGGING_CONFIG' in os.environ:
filename = os.environ['LOGGING_CONFIG']
with open(filename) as f:
logging_config = json.loads(f.read())
logging.config.dictConfig(logging_config)
"""
API Endpoints
=========================
.. contents:: :local:
/api/things
---------------------
.. autofunction:: compendium_v2.routes.api.things
"""
import binascii
import hashlib
import random
import time
from flask import Blueprint, jsonify
from compendium_v2.routes import common
routes = Blueprint("compendium-v2-api", __name__)
THING_LIST_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'thing': {
'type': 'object',
'properties': {
'id': {'type': 'string'},
'time': {'type': 'number'},
'state': {'type': 'boolean'},
'data1': {'type': 'string'},
'data2': {'type': 'string'},
'data3': {'type': 'string'}
},
'required': ['id', 'time', 'state', 'data1', 'data2', 'data3'],
'additionalProperties': False
}
},
'type': 'array',
'items': {'$ref': '#/definitions/thing'}
}
@routes.after_request
def after_request(resp):
return common.after_request(resp)
@routes.route("/things", methods=['GET', 'POST'])
@common.require_accepts_json
def things():
"""
handler for /api/things requests
response will be formatted as:
.. asjson::
compendium_v2.routes.api.THING_LIST_SCHEMA
:return:
"""
def _hash(s, length):
m = hashlib.sha256()
m.update(s.encode('utf-8'))
digest = binascii.b2a_hex(m.digest()).decode('utf-8')
return digest[-length:].upper()
def _make_thing(idx):
six_months = 24 * 3600 * 180
return {
'id': _hash(f'id-{idx}', 4),
'time': int(time.time() + random.randint(-six_months, six_months)),
'state': bool(idx % 2),
'data1': _hash(f'data1-{idx}', 2),
'data2': _hash(f'data2-{idx}', 8),
'data3': _hash(f'data3-{idx}', 32)
}
response = map(_make_thing, range(20))
return jsonify(list(response))
"""
Utilities used by multiple route blueprints.
"""
import functools
import logging
from flask import request, Response
logger = logging.getLogger(__name__)
_DECODE_TYPE_XML = 'xml'
_DECODE_TYPE_JSON = 'json'
def require_accepts_json(f):
"""
used as a route handler decorator to return an error
unless the request allows responses with type "application/json"
:param f: the function to be decorated
:return: the decorated function
"""
@functools.wraps(f)
def decorated_function(*args, **kwargs):
# TODO: use best_match to disallow */* ...?
if not request.accept_mimetypes.accept_json:
return Response(
response="response will be json",
status=406,
mimetype="text/html")
return f(*args, **kwargs)
return decorated_function
def after_request(response):
"""
Generic function to do additional logging of requests & responses.
:param response:
:return:
"""
if response.status_code != 200:
try:
data = response.data.decode('utf-8')
except Exception:
# never expected to happen, but we don't want any failures here
logging.exception('INTERNAL DECODING ERROR')
data = 'decoding error (see logs)'
logger.warning('"%s %s" "%s" %s' % (
request.method,
request.path,
data,
str(response.status_code)))
return response
"""
Default Endpoints
=========================
.. contents:: :local:
/version
---------------------
.. autofunction:: compendium_v2.routes.default.version
"""
import pkg_resources
from flask import Blueprint, jsonify, render_template
from compendium_v2.routes import common
routes = Blueprint("compendium-v2-default", __name__)
API_VERSION = '0.1'
VERSION_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'api': {
'type': 'string',
'pattern': r'\d+\.\d+'
},
'module': {
'type': 'string',
'pattern': r'\d+\.\d+'
}
},
'required': ['api', 'module'],
'additionalProperties': False
}
@routes.after_request
def after_request(resp):
return common.after_request(resp)
@routes.route("/", methods=['GET'])
def index():
return render_template('index.html')
@routes.route("/version", methods=['GET', 'POST'])
@common.require_accepts_json
def version():
"""
handler for /version requests
response will be formatted as:
.. asjson::
compendium_v2.routes.default.VERSION_SCHEMA
:return:
"""
version_params = {
'api': API_VERSION,
'module':
pkg_resources.get_distribution('compendium-v2').version
}
return jsonify(version_params)
"""
Service Matrix Endpoints
=========================
These endpoints are intended for getting service matrix.
.. contents:: :local:
/service-matrix
---------------------
.. autofunction:: compendium_v2.routes.service_matrix
"""
import json
import logging
import os
from flask import Blueprint
from compendium_v2.routes import common
SERVICE_MATRIX_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
"type": "object",
"properties": {
"key": {
"type": "string"
},
"nrens": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"nren_id": {
"type": "integer"
},
"tags": {
"type": "array",
"items": {}
}
},
"required": [
"name",
"nren_id",
"tags"
],
'additionalProperties': True
}
}
}
}
DUMMY_DATA_FILENAME = os.path.abspath(os.path.join(
os.path.dirname(__file__),
'..',
'datasources',
'dummy-service-matrix.json'
))
routes = Blueprint("compendium-v2-service-matrix", __name__)
logger = logging.getLogger(__name__)
file_name = open(DUMMY_DATA_FILENAME)
service_matrix_response = json.loads(file_name.read())
@routes.route("", methods=['GET'])
@common.require_accepts_json
def get_service_matrix():
"""
handler for /service-matrix requests
response will be formatted as:
.. asjson::
compendium_v2.routes.api.THING_LIST_SCHEMA
:return:
"""
return service_matrix_response
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment