From 99a8b201e8288a8b9b98c65131ffa1a5487ae186 Mon Sep 17 00:00:00 2001 From: JPy Date: Mon, 18 Jun 2018 15:27:14 +0200 Subject: [PATCH] first commit for solidata --- .flaskenv | 0 .gitignore | 116 +++++++++++ LICENCE | 21 ++ README.md | 17 ++ appserver.py | 45 +++++ log_config.py | 47 +++++ logs/help.md | 0 manage.py | 0 requirements.txt | 35 ++++ solidata_api/__init__.py | 1 + solidata_api/admin/__init__.py | 0 solidata_api/api/__init__.py | 0 solidata_api/api/api_projects/__init__.py | 55 +++++ solidata_api/api/api_projects/proj_list.py | 26 +++ solidata_api/api/api_users/__init__.py | 35 ++++ solidata_api/api/api_users/endpoints.py | 130 ++++++++++++ solidata_api/api/api_users/models.py | 35 ++++ solidata_api/api/api_users/users_register.py | 40 ++++ solidata_api/application.py | 98 +++++++++ solidata_api/config.py | 44 ++++ solidata_api/core/__init__.py | 0 solidata_api/core/ext_services/__init__.py | 0 solidata_api/core/queries_db/__init__.py | 46 +++++ solidata_api/parsers/__init__.py | 0 solidata_api/parsers/parser_pagination.py | 33 +++ solidata_api/serializers/__init__.py | 0 solidata_api/serializers/schema_corr_dicts.py | 0 solidata_api/serializers/schema_datamodels.py | 0 .../serializers/schema_datasets_inputs.py | 0 .../serializers/schema_datasets_outputs.py | 0 solidata_api/serializers/schema_pagination.py | 16 ++ solidata_api/serializers/schema_projects.py | 40 ++++ solidata_api/serializers/schema_recipes.py | 0 solidata_api/serializers/schema_users.py | 191 ++++++++++++++++++ 34 files changed, 1071 insertions(+) create mode 100644 .flaskenv create mode 100644 .gitignore create mode 100644 LICENCE create mode 100644 README.md create mode 100644 appserver.py create mode 100755 log_config.py create mode 100644 logs/help.md create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 solidata_api/__init__.py create mode 100644 solidata_api/admin/__init__.py create mode 100644 solidata_api/api/__init__.py create mode 100644 solidata_api/api/api_projects/__init__.py create mode 100644 solidata_api/api/api_projects/proj_list.py create mode 100644 solidata_api/api/api_users/__init__.py create mode 100644 solidata_api/api/api_users/endpoints.py create mode 100644 solidata_api/api/api_users/models.py create mode 100644 solidata_api/api/api_users/users_register.py create mode 100644 solidata_api/application.py create mode 100644 solidata_api/config.py create mode 100644 solidata_api/core/__init__.py create mode 100644 solidata_api/core/ext_services/__init__.py create mode 100644 solidata_api/core/queries_db/__init__.py create mode 100644 solidata_api/parsers/__init__.py create mode 100644 solidata_api/parsers/parser_pagination.py create mode 100644 solidata_api/serializers/__init__.py create mode 100644 solidata_api/serializers/schema_corr_dicts.py create mode 100644 solidata_api/serializers/schema_datamodels.py create mode 100644 solidata_api/serializers/schema_datasets_inputs.py create mode 100644 solidata_api/serializers/schema_datasets_outputs.py create mode 100644 solidata_api/serializers/schema_pagination.py create mode 100644 solidata_api/serializers/schema_projects.py create mode 100644 solidata_api/serializers/schema_recipes.py create mode 100644 solidata_api/serializers/schema_users.py diff --git a/.flaskenv b/.flaskenv new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a44bd84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,116 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# config_prod +config_prod.py + +# snippets folder +_snippets/ + +# logs +*.log + +# C extensions +*.so + +# Flask stuff +# .flaskenv + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..2f4c2dd --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Entrepreneurs d’Intérêt Général + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6bd2fa --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +

SOLIDATA
backend

+ + +------- +## PRESENTATION + +A public service to allow you to open data as simple as posible + + +------ + +## TECHNICAL POINTS + +#### Tech stack +- _Language_ : **Python3**... yes ... I know ... hmmm ... gnnn ... don't judge me ? +- _Framework_ : **[Flask](http://flask.pocoo.org/)**... minimalistic Python framework +- _API_ : **[Flask-RestPlus](http://flask-restplus.readthedocs.io/en/stable/)**... Swagger documentation integrated diff --git a/appserver.py b/appserver.py new file mode 100644 index 0000000..58921e4 --- /dev/null +++ b/appserver.py @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- + +""" +appserver.py +- creates an application instance and runs the dev server +""" + +import os + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### SET LOGGER +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### + +from log_config import log, pformat +# log.debug('>>> TESTING LOGGER') + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### RUN APPSERVER +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### + +if __name__ == '__main__': + + """ + runner for the SOLIDATA backend Flask app + + in command line just type : + "python appserver.py" + + """ + + log.debug("\n--- STARTING SOLIDATA API ---\n") + + from solidata_api.application import create_app + + app = create_app() + + app_port = int(app.config["DOMAIN_PORT"]) + app_host = app.config["DOMAIN_ROOT"] + app_debug = app.config["DEBUG"] + + + # simple flask runner + print("== "*30) + app.run( debug=app_debug, host=app_host, port=app_port, threaded=True ) \ No newline at end of file diff --git a/log_config.py b/log_config.py new file mode 100755 index 0000000..6b6fe4a --- /dev/null +++ b/log_config.py @@ -0,0 +1,47 @@ +from pprint import pprint, pformat + +import logging +from logging.config import dictConfig + +import colorlog +from colorlog import ColoredFormatter + + +# cf : https://stackoverflow.com/questions/17668633/what-is-the-point-of-setlevel-in-a-python-logging-handler + +### create a formatter for future logger +formatter = ColoredFormatter( + "%(log_color)s%(levelname)1.1s ::: %(name)s %(asctime)s ::: %(module)s:%(lineno)d -in- %(funcName)s ::: %(reset)s %(white)s%(message)s", + datefmt='%y-%m-%d %H:%M:%S', + reset=True, + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red,bg_white', + }, + secondary_log_colors={}, + style='%' +) + +### create handler +handler = colorlog.StreamHandler() +handler.setFormatter(formatter) + +### create logger +log = colorlog.getLogger("log") +log.addHandler(handler) + +### set logging level +log.setLevel(logging.DEBUG) + +log_file_I = logging.handlers.RotatingFileHandler('logs/info_logs.log') +log_file_I.setFormatter(formatter) +log_file_I.setLevel(logging.INFO) +log.addHandler(log_file_I) + +log_file_W = logging.handlers.RotatingFileHandler('logs/warning_logs.log') +log_file_W.setFormatter(formatter) +log_file_W.setLevel(logging.INFO) +log.addHandler(log_file_W) diff --git a/logs/help.md b/logs/help.md new file mode 100644 index 0000000..e69de29 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b52f31c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,35 @@ +aniso8601==3.0.0 +bcrypt==3.1.4 +blinker==1.4 +certifi==2018.4.16 +cffi==1.11.5 +chardet==3.0.4 +click==6.7 +colorlog==3.1.4 +Flask==1.0.2 +Flask-Admin==1.5.1 +Flask-Login==0.4.1 +Flask-Mail==0.9.1 +Flask-PyMongo==0.5.2 +flask-restplus==0.11.0 +Flask-SocketIO==3.0.1 +idna==2.7 +itsdangerous==0.24 +Jinja2==2.10 +jsonschema==2.6.0 +MarkupSafe==1.0 +numpy==1.14.5 +pandas==0.23.1 +pycparser==2.18 +PyJWT==1.6.4 +pymongo==3.6.1 +python-dateutil==2.7.3 +python-dotenv==0.8.2 +python-engineio==2.1.1 +python-socketio==1.9.0 +pytz==2018.4 +requests==2.19.0 +six==1.11.0 +urllib3==1.23 +Werkzeug==0.14.1 +WTForms==2.2.1 diff --git a/solidata_api/__init__.py b/solidata_api/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/solidata_api/__init__.py @@ -0,0 +1 @@ + diff --git a/solidata_api/admin/__init__.py b/solidata_api/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/api/__init__.py b/solidata_api/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/api/api_projects/__init__.py b/solidata_api/api/api_projects/__init__.py new file mode 100644 index 0000000..14b0beb --- /dev/null +++ b/solidata_api/api/api_projects/__init__.py @@ -0,0 +1,55 @@ +# -*- encoding: utf-8 -*- + +""" +api_projects/__init__.py +- provides the API endpoints for consuming and producing + REST requests and responses +""" + +from flask import Blueprint +from flask_restplus import Api + +### create blueprint and api wrapper +blueprint = Blueprint( 'api_projects', __name__) +api = Api( blueprint, + title="SOLIDATA - PROJECTS API", + version="0.1", + description="create, list, delete, edit... projects", + doc='/documentation', + default='projects_list' + ) + +### import data schemas +### TO DO + +### mocking a project definition +projects = [ + { + "id" : 1, + "proj_title" : "my project", + "owner" : "email", + "collaborators" : [], + + "datamodel" : "dfghjkl", # datamodel id in DB + "datasets_inputs" : [], # list of datasets_input ids in DB + "corr_dicts" : [], # list of corr_dict ids in DB + + "recipes" : { + "on_datamodel" : {}, + "on_datasets" : {}, + "on_corr_dict" : {}, + }, + + "dataset_output" : "", # unique dataset output id in DB + + "exports" : [], # description of exports settings + + } +] + + +### import api namespaces +from .proj_list import api as api_proj_list + +### add namespaces to api wrapper +api.add_namespace(api_proj_list) \ No newline at end of file diff --git a/solidata_api/api/api_projects/proj_list.py b/solidata_api/api/api_projects/proj_list.py new file mode 100644 index 0000000..30e0028 --- /dev/null +++ b/solidata_api/api/api_projects/proj_list.py @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- + +""" +proj_list.py +- provides the API endpoints for consuming and producing + REST requests and responses +""" + +from flask_restplus import Namespace, Resource, fields + +from . import projects + +api = Namespace('projects_list', description='Projects list ') + + + +### ROUTES +@api.route('/') +class ProjectsList(Resource): + + # @api.marshal_with(project_model, envelope="projects_list") + def get(self): + """ + list of all projects in db + """ + return projects \ No newline at end of file diff --git a/solidata_api/api/api_users/__init__.py b/solidata_api/api/api_users/__init__.py new file mode 100644 index 0000000..5eb513f --- /dev/null +++ b/solidata_api/api/api_users/__init__.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +""" +api_users/__init__.py +- provides the API endpoints for consuming and producing + REST requests and responses +""" + +from log_config import log, pformat +log.debug(">>> api_users ... creating api blueprint for USERS") + +from flask import Blueprint +from flask_restplus import Api + +### import db collections dict +from solidata_api.application import mongo + + +### create blueprint and api wrapper +blueprint = Blueprint( 'api_users', __name__ ) +api = Api( blueprint, + title="SOLIDATA - USERS API", + version="0.1", + description="create, list, delete, edit... users", + doc='/documentation', + default='users' +) + + +### import api namespaces / add namespaces to api wrapper +from .endpoints import ns as ns_users_list +api.add_namespace(ns_users_list) + +# from .users_register import ns as ns_user_register +# api.add_namespace(ns_user_register) \ No newline at end of file diff --git a/solidata_api/api/api_users/endpoints.py b/solidata_api/api/api_users/endpoints.py new file mode 100644 index 0000000..db6eddf --- /dev/null +++ b/solidata_api/api/api_users/endpoints.py @@ -0,0 +1,130 @@ +# -*- encoding: utf-8 -*- + +""" +users_list.py +- provides the API endpoints for consuming and producing + REST requests and responses +""" + +from log_config import log +log.debug(">>> api_users ... creating api endpoints for USERS") + +from bson import json_util +from bson.objectid import ObjectId +from bson.json_util import dumps + +from flask import current_app, request +from flask_restplus import Namespace, Resource, fields, marshal, reqparse +from werkzeug.security import generate_password_hash, check_password_hash + +### import mongo utils +# from . import mongo +from solidata_api.application import mongo +from solidata_api.core.queries_db import * # mongo_users, etc... + +# ### import data serializers +from solidata_api.serializers.schema_users import * + +### create namespace +ns = Namespace('users', description='Users list ') + +### import parsers +from solidata_api.parsers.parser_pagination import pagination_arguments + +### import models +from .models import * + + +### ROUTES +@ns.route('/') +class UsersList(Resource): + + @ns.doc('users_list') + @ns.expect(pagination_arguments) + @ns.marshal_list_with( model_user, skip_none=True)#, envelop="users_list" ) + def get(self): + """ + list of all users in db + without _id + """ + ### DEBUGGING + print() + log.debug( self.__class__.__name__ ) + + ### get pagination + args = pagination_arguments.parse_args(request) + page = args.get('page', 1) + per_page = args.get('per_page', 10) + + ### retrieve from db + cursor = mongo_users.find({}, {"_id": 0 }) + users = list(cursor) + log.debug( users ) + + return users, 200 + + + @ns.doc('create_user') + @ns.expect(model_new_user, validate=True) + @ns.marshal_with(model_new_user, envelope="new_user", code=201) + def post(self): + """ + create / register a new user + """ + print() + log.debug("post") + log.debug ("payload : \n{}".format(pformat(ns.payload))) + + payload_email = ns.payload["email"] + log.debug("email : %s", payload_email ) + + ### chek if user already exists in db + existing_user = mongo.db["users"].find_one({"infos.email" : payload_email}) + log.debug(existing_user) + + if existing_user is None : + + payload_pwd = ns.payload["password"] + + new_user = marshal(ns.payload, model_user) + log.debug ("new_user : \n{}".format(pformat(new_user))) + + # create hashpassword + hashpass = generate_password_hash(payload_pwd, method='sha256') + + return "new user added... (fake)" + + else : + + return "this email already exists" + + +### TO DO + +@ns.route("/") +@ns.response(404, 'user not found') +@ns.param('id', 'The user identifier') +class User(Resource) : + + '''Show a single user item and lets you delete them''' + @ns.doc('get_user') + @ns.marshal_with(model_new_user) + def get(self, id): + '''Fetch a given user''' + return "fetching user {} ".format(id) # DAO.get(id) + + @ns.doc('delete_user') + @ns.response(204, 'Todo deleted') + def delete(self, id): + '''Delete a user given its identifier''' + # DAO.delete(id) + return '', 204 + + @ns.expect(model_new_user) + @ns.marshal_with(model_new_user) + def put(self, id): + '''Update an user given its identifier''' + + return "updating user " # DAO.update(id, api.payload) + + diff --git a/solidata_api/api/api_users/models.py b/solidata_api/api/api_users/models.py new file mode 100644 index 0000000..e14862c --- /dev/null +++ b/solidata_api/api/api_users/models.py @@ -0,0 +1,35 @@ +# -*- encoding: utf-8 -*- + +""" +api_users/models.py +- provides the models for PAGINATION definition in DB and Flask-Restplus +""" + +from flask_restplus import fields + +### import data serializers +from solidata_api.serializers.schema_users import * + +### iomport API namespace +from .endpoints import ns + +### create models from serializers +# nested models : https://github.com/noirbizarre/flask-restplus/issues/8 + +# model_user_infos = ns.model( "User model", user_infos) #, mask="{name,surname,email}" ) +model_new_user = ns.model( "User_register", user_register ) + +model_user = ns.model('User', { + 'infos': fields.Nested( + ns.model('User_public_data', user_basics ) + ), + 'auth': fields.Nested( + ns.model('User_authorizations', user_auth ) + ), + 'preferences': fields.Nested( + ns.model('User_preferences', user_preferences ) + ), + 'datasets': fields.Nested( + ns.model('User_datasets', user_datasets ) + ), +}) \ No newline at end of file diff --git a/solidata_api/api/api_users/users_register.py b/solidata_api/api/api_users/users_register.py new file mode 100644 index 0000000..88f985e --- /dev/null +++ b/solidata_api/api/api_users/users_register.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- + +""" +user_register.py +- provides the API endpoints for consuming and producing + REST requests and responses +""" + +from flask_restplus import Namespace, Resource +import bcrypt + +from . import user_schema + +ns = Namespace('user_register', description='User registration ') + +model = ns.model( "New user", user_schema, mask="{name,surname,password}" ) + + +@ns.route('/register') +class Register(Resource): + """ + register a new user in DB + """ + + @ns.expect(model) + def post(self, validate=True) : + """ + register a new user in DB + """ + print(api.payload) + new_user = api.payload + + ### read payload + + ### hash password + + ### add to db + # users.append(new_user) + + return {"message" : "a new user has been created..."}, 201 \ No newline at end of file diff --git a/solidata_api/application.py b/solidata_api/application.py new file mode 100644 index 0000000..5fb711d --- /dev/null +++ b/solidata_api/application.py @@ -0,0 +1,98 @@ +# -*- encoding: utf-8 -*- + +""" +application.py +- creates a Flask app instance and registers the database object +""" + +from log_config import log, pprint, pformat + +from flask import Flask, g, current_app + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### LOGIN MANAGER +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### + +from flask_login import LoginManager, login_user, logout_user, login_required, \ + current_user + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### FLASK-ADMIN IMPORTS +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +from flask_admin import Admin, AdminIndexView +from flask_admin.model import typefmt +from flask_admin.model.widgets import XEditableWidget + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### FLASK-PYMONGO IMPORTS +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +from flask_pymongo import PyMongo +# from solidata_api.core.queries_db import MongoCollection + +# declare mongo empty connector +mongo = PyMongo() + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### FLASK-MAIL IMPORTS +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +# from flask_mail import Mail +# mail = Mail() + + + +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +### CREATE APP +### + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ### +# application factory, see: http://flask.pocoo.org/docs/patterns/appfactories/ + +def create_app(app_name='SOLIDATA_API'): + + log.debug ("... creating app ...") + + app = Flask(app_name) + + app.config.from_object('solidata_api.config.BaseConfig') + + log.debug("... app.config :\n %s", pformat(app.config)) + print() + + mongo.init_app(app) + + # access mongodb collections + with app.app_context() : + + from solidata_api.core.queries_db import db, \ + mongo_users,mongo_licences,mongo_projects,mongo_datamodels, \ + mongo_datamodels_fields,mongo_connectors, \ + mongo_datasets_inputs,mongo_datasets_outputs, \ + mongo_recipes,mongo_corr_dicts + + + ## DEBUG + find_one_user = mongo_users.find({'infos.name': "Julien"}) + # find_one_user = db["mongo_users"].find({'infos.name': "Julien"}) + log.debug("DEBUG : find_one_user : \n%s", pformat(list(find_one_user))) + print() + + + ### registering all blueprints + from solidata_api.api.api_users import blueprint as api_users + app.register_blueprint( api_users, url_prefix="/api/users" ) + + # from solidata_api.api.api_projects import blueprint as api_projects + # app.register_blueprint( api_projects, url_prefix='/api/projects') + + + + ### DEBUG + # @app.before_request + # def debug_stuff(): + # # pass + # log.debug ("\n%s", pformat(current_app.__dict__)) + + + return app \ No newline at end of file diff --git a/solidata_api/config.py b/solidata_api/config.py new file mode 100644 index 0000000..f286d8f --- /dev/null +++ b/solidata_api/config.py @@ -0,0 +1,44 @@ +""" +config.py +- settings for the flask application object +""" + +class BaseConfig(object): + + DEBUG = True + + # used for encryption and session management + + """ RESTPLUS CONFIG """ + # SWAGGER_UI_DOC_EXPANSION = 'list' + SWAGGER_UI_JSONEDITOR = True + SWAGGER_UI_OPERATION_ID = True + SWAGGER_UI_REQUEST_DURATION = True + + """ APP SECRET KEY """ + SECRET_KEY = "app_very_secret_key" + + """ SHARED JWT SECRET KEY : this key must be shared with openscraper and solidata """ + JWT_SECRET_KEY = "a_key_shared_with_front_and_openscraper_and_solidata" + + """ HOST """ + DOMAIN_ROOT = "localhost" + DOMAIN_PORT = "4000" + SERVER_NAME = "localhost:4000" ### if True need to set SESSION_COOKIE_DOMAIN + cf : https://stackoverflow.com/questions/47666210/cookies-not-saved-in-the-browser + DOMAIN_NAME = "http://localhost:4000" + SERVER_NAME_TEST = "True" + + """ MONGODB """ + MONGO_DBNAME = 'solidata' + MONGO_URI = 'mongodb://localhost:27017/solidata' + # collections + MONGO_COLL_USERS = "users" + MONGO_COLL_LICENCES = "licences" + MONGO_COLL_PROJECTS = "projects" + MONGO_COLL_DATAMODELS = "datamodels" + MONGO_COLL_DATAMODELS_FIELDS = "datamodels_fields" + MONGO_COLL_CONNECTORS = "connectors" + MONGO_COLL_DATASETS_INPUTS = "datasets_inputs" + MONGO_COLL_DATASETS_OUTPUTS = "datasets_outputs" + MONGO_COLL_RECIPES = "recipes" + MONGO_COLL_CORR_DICTS = "corr_dicts" diff --git a/solidata_api/core/__init__.py b/solidata_api/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/core/ext_services/__init__.py b/solidata_api/core/ext_services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/core/queries_db/__init__.py b/solidata_api/core/queries_db/__init__.py new file mode 100644 index 0000000..e6dbb9b --- /dev/null +++ b/solidata_api/core/queries_db/__init__.py @@ -0,0 +1,46 @@ +# -*- encoding: utf-8 -*- + +""" +query/__init__.py.py +- provides the MONGO QUERIES for + REST requests +""" + +from log_config import log, pformat +log.debug(">>> queries_db ... loading mongodb collections as global variables") + +from flask import current_app as app + +from solidata_api.application import mongo + + + +mongo_users = mongo.db[ app.config["MONGO_COLL_USERS"] ] +mongo_licences = mongo.db[ app.config["MONGO_COLL_LICENCES"] ] +mongo_projects = mongo.db[ app.config["MONGO_COLL_PROJECTS"] ] +mongo_datamodels = mongo.db[ app.config["MONGO_COLL_DATAMODELS"] ] +mongo_datamodels_fields = mongo.db[ app.config["MONGO_COLL_DATAMODELS_FIELDS"] ] +mongo_connectors = mongo.db[ app.config["MONGO_COLL_CONNECTORS"] ] +mongo_datasets_inputs = mongo.db[ app.config["MONGO_COLL_DATASETS_INPUTS"] ] +mongo_datasets_outputs = mongo.db[ app.config["MONGO_COLL_DATASETS_OUTPUTS"] ] +mongo_recipes = mongo.db[ app.config["MONGO_COLL_RECIPES"] ] +mongo_corr_dicts = mongo.db[ app.config["MONGO_COLL_CORR_DICTS"] ] + +db = { + "mongo_users" : mongo_users, + "mongo_licences" : mongo_licences, + "mongo_projects" : mongo_projects, + "mongo_datamodels" : mongo_datamodels, + "mongo_datamodels_fields" : mongo_datamodels_fields, + "mongo_connectors" : mongo_connectors, + "mongo_datasets_inputs" : mongo_datasets_inputs, + "mongo_datasets_outputs" : mongo_datasets_outputs, + "mongo_recipes" : mongo_recipes, + "mongo_corr_dicts" : mongo_corr_dicts, + } + +def select_collection(coll_name): + coll = db[coll_name] + return coll + + diff --git a/solidata_api/parsers/__init__.py b/solidata_api/parsers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/parsers/parser_pagination.py b/solidata_api/parsers/parser_pagination.py new file mode 100644 index 0000000..8d762dd --- /dev/null +++ b/solidata_api/parsers/parser_pagination.py @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- + +""" +pagination.py +- provides the PAGINATION parser for + REST requests +""" + +from flask_restplus import reqparse + +pagination_arguments = reqparse.RequestParser() +pagination_arguments.add_argument( + 'page', + type=int, + required=False, + default=1, + help='Page number' +) +pagination_arguments.add_argument( + 'bool', + type=bool, + required=False, + default=True, + help='Page number' +) +pagination_arguments.add_argument( + 'per_page', + type=int, + required=False, + choices=[2, 10, 20, 30, 40, 50], + default=10, + help='Results per page {error_msg}' +) \ No newline at end of file diff --git a/solidata_api/serializers/__init__.py b/solidata_api/serializers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_corr_dicts.py b/solidata_api/serializers/schema_corr_dicts.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_datamodels.py b/solidata_api/serializers/schema_datamodels.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_datasets_inputs.py b/solidata_api/serializers/schema_datasets_inputs.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_datasets_outputs.py b/solidata_api/serializers/schema_datasets_outputs.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_pagination.py b/solidata_api/serializers/schema_pagination.py new file mode 100644 index 0000000..4968272 --- /dev/null +++ b/solidata_api/serializers/schema_pagination.py @@ -0,0 +1,16 @@ +# -*- encoding: utf-8 -*- + +""" +schema_pagination.py +- provides the serializers for PAGINATION definition in DB and Flask-Restplus +""" + +from log_config import log, pformat + +from flask_restplus import fields + + +'page' = fields.Integer(description='Number of this page of results') +'pages' = fields.Integer(description='Total number of pages of results') +'per_page' = fields.Integer(description='Number of items per page of results') +'total' = fields.Integer(description='Total number of results') \ No newline at end of file diff --git a/solidata_api/serializers/schema_projects.py b/solidata_api/serializers/schema_projects.py new file mode 100644 index 0000000..8e3d99c --- /dev/null +++ b/solidata_api/serializers/schema_projects.py @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- + +""" +model_projects.py +- provides the model for PROJECT definition in DB and Flask-Restplus +""" + +from flask_restplus import fields + + +projects = [ + { + "id" : fields.String("Oid of the project"), + "proj_title" : fields.String("title of the project"), + "owner" : fields.String("id of project's owner"), + "collaborators" : [], + + "datamodel" : fields.String("id of the project"), # datamodel id in DB + "datasets_inputs" : [], # list of datasets_input ids in DB + "corr_dicts" : [], # list of corr_dict ids in DB + + "recipes" : { + "on_datamodel" : {}, + "on_datasets" : {}, + "on_corr_dict" : {}, + }, + + "dataset_output" : fields.String("title of the project"), # unique dataset output id in DB + + "exports" : [], # description of exports settings + + } +] + + +project_definition = { + "name" : fields.String(attribute="name of the user"), + "surname" : fields.String(attribute="surname of the user"), + "email" : fields.String(attribute="email of the user"), +} \ No newline at end of file diff --git a/solidata_api/serializers/schema_recipes.py b/solidata_api/serializers/schema_recipes.py new file mode 100644 index 0000000..e69de29 diff --git a/solidata_api/serializers/schema_users.py b/solidata_api/serializers/schema_users.py new file mode 100644 index 0000000..ae03029 --- /dev/null +++ b/solidata_api/serializers/schema_users.py @@ -0,0 +1,191 @@ +# -*- encoding: utf-8 -*- + +""" +schema_users.py +- provides the model for USER definition in DB and Flask-Restplus +""" + +from log_config import log, pformat + +from flask_restplus import fields, marshal +import json + +user_auth_levels = [ + "admin", "staff", "collective", "registred", "guest" +] + +name = fields.String( + description="name of the user", + attribute="name", + default='Anonymous User', + required=False, + ) +surname = fields.String( + description="surname of the user", + attribute="surname", + required=False, + ) +email = fields.String( + description="email of the user", + attribute="email", + required=False, + ) + + +password = fields.String( + description="password of the user", + attribute="pwd", + required=True, + ) +auth_level = fields.String( + description="authorization level of the user", + attribute="auth_level", + default="guest", + ) +token = fields.String( + description="public token of user", + attribute="token", + default="no_token", + ) + + +language = fields.String( + description="language preference", + attribute="lang", + default="en", + ) + + +structures = fields.List( + fields.String( + description="structure / organisation the user"), + default=[] + ) +profiles = fields.List( + fields.String( + description="profile of the user"), + default=[] + ) + + +proj_list = fields.List( + fields.String( + description="ids of the projects created by the user"), + attribute="proj_list", + default=[] + ) +dm_list = fields.List( + fields.String( + description="ids of the datamodels created by the user"), + attribute="dm_list", + default=[] + ) +dsi_list = fields.List( + fields.String( + description="ids of the datasets_in imported by the user"), + attribute="dsi_list", + default=[] + ) +dso_list = fields.List( + fields.String( + description="ids of the datasets_out exported by the user"), + attribute="dso_list", + default=[] + ) +dc_list = fields.List( + fields.String( + description="ids of the correspondance_dicts created by the user"), + attribute="dc_list", + default=[] + ) +rec_list = fields.List( + fields.String( + description="ids of the recipes created by the user"), + attribute="rec_list", + default=[] + ) + + +user_basics = { + "name" : name, + "surname" : surname, + "email" : email, +} + +user_register = { + "name" : name, + "surname" : surname, + "email" : email, + "password" : password, +} + +user_preferences = { + "language" : language +} + +user_professional = { + "structures" : structures, + "profiles" : profiles +} + +user_auth = { + "password" : password, + "auth_level" : auth_level, + "token" : token, +} + +user_datasets = { + "projects" : proj_list, + "datamodels" : dm_list, + "datasets_inputs" : dsi_list, + "datasets_inputs" : dso_list, + "correspondance_dicts" : dc_list, + "recipes" : rec_list, +} + + + + + +# user_infos = { + +# "name" : name, +# "surname" : surname, +# "email" : email, + +# "language" : language, + +# "auth_level" : auth_level, +# "token" : token, + +# "projects" : proj_list, +# "datamodels" : dm_list, +# "datasets_inputs" : dsi_list, +# "datasets_inputs" : dso_list, +# "correspondance_dicts" : dc_list, +# "recipes" : rec_list, + +# } + +# ### --------------------------------- +# fake_user = { +# # "_id" : ObjectId("5b2173a00415489360a99f0d"), +# "name" : "Julien", +# "surname" : "Paris", +# "email" : "julien@cget.gouv.fr", +# "auth_level" : "admin", +# "proj_list" : [ +# "001", +# "00Z" +# ] +# } + +# user_nested = {} +# user_nested['infos'] = fields.Nested(user_basics) +# user_nested['preferences'] = fields.Nested(user_basics) +# user_nested['auth'] = fields.Nested(user_auth) +# user_nested['datasets'] = fields.Nested(user_datasets) + +# user_nest = {} +# user_nest['infos'] = user_basics +# log.debug("test on fake_user : \n %s", pformat(marshal(fake_user, user_nest)) )