[release][0.1.0] First HalfAPI release

Squashed commit of the following:

commit 68032dc55a07565ccd17a188407d9ac2537b62e6
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 15:40:26 2020 +0200

    [release][0.1.0] First HalfAPI release

commit a046a81114a3ae22bbc84a53f1e85217a0954dbc
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 15:26:15 2020 +0200

    [doc] màj du readme

commit ed45b3011125c071aa53df8087d28bfa1150b373
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 11:03:17 2020 +0200

    [wip] rm apidb

commit 7df4b9bacf3d26f09ea07856587505f74284cab4
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 10:58:46 2020 +0200

    [db] forgot foreign key router->domain

commit 604b9a90f405121725e4b2126d73a5a83eec398f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 10:51:01 2020 +0200

    [wip] routes mounting fixed

commit c50a5572633d7dcc3cad48ef79c5ea1ca098284d
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Thu Jul 23 10:29:09 2020 +0200

    [wip][db][nf] http_verb, fct_name are in api.route

commit 2b5b78db2f9c280dd5eb9d8bafc9174d9afc092f
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Wed Jul 22 17:37:21 2020 +0200

    [wip] refactor du 22 juillet 2222

commit d019b7e333ab37f106895c84521cef1bfe768caa
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Wed Jul 22 14:48:51 2020 +0200

    [wip] remove "version" from app

commit 98ccd61dcf369b8c4aac817a0c8409b6fed00f50
Author: Joël Maïzi <joel.maizi@lirmm.fr>
Date:   Wed Jul 22 14:45:28 2020 +0200

    Remove api.version from database.

commit aa0d4f8dbba8b8ec878835bb58b69facff7e675e
Author: Maxime Alves LIRMM@home <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 21:46:22 2020 +0200

    [db] added router as api.acl primary_key part

commit a97984e9de0e6a00bddca7dece0c715c9c16cbe1
Author: Maxime Alves LIRMM@home <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 21:41:31 2020 +0200

    [wip] moved all acl treatment to acl_caller_middleware, fix route mounting, fix typo in logs

commit 3dd310e80aaf6cb32f6c4ac23c1e2a924cebfde1
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 13:10:26 2020 +0200

    [wip][nf] gestion des routes avec routers

commit c2687c4a24126fbc3e57257bf23c267b334df522
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 12:39:53 2020 +0200

    [db] ajout de la table api.router

commit 9a10f76cf7790f75f23b72e19b0a58978752565c
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 12:19:53 2020 +0200

    [db] renommage des champs d'acl

commit c4e8c26a24835559d2e9b251df0eb462fe7e667d
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 12:13:38 2020 +0200

    [wip][nf] modification du systeme de montage des routes

commit b7e8352ba1e427e9883797a44bb0f3da5edd576d
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 11:15:30 2020 +0200

    [wip][dbupdate] insertion des routes au nouveau format

commit 28947444c4c062e6ced74f9bfdef11a36ddc438b
Author: Maxime Alves LIRMM <maxime.alves@lirmm.fr>
Date:   Tue Jul 21 10:32:57 2020 +0200

    [wip][dbupdate] Suppression des routes hors domaine
This commit is contained in:
Maxime Alves LIRMM 2020-07-23 17:54:47 +02:00
parent 5d9f4631be
commit 0282da6e3d
30 changed files with 366 additions and 1807 deletions

8
CHANGELOG.md Normal file
View File

@ -0,0 +1,8 @@
# HalfAPI
## 0.1.0
- Mounts domain routers with their ACLs as decorator
- Configuration example files for systemd and a system-wide halfapi install
- Runs projects
- Handles JWT authentication middleware

14
LICENSE Normal file
View File

@ -0,0 +1,14 @@
Copyright (c) 2015-2020 Joël Maïzi <joel.maizi@collorg.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -1,7 +1,6 @@
# HalfAPI # HalfAPI
This Python-based ASGI application aims to provide the core functionality to This Python-based ASGI application aims to provide the core functionality to
multiple API domains. multiple API domains.
@ -13,33 +12,27 @@ The name "halfAPI" comes from the deep relationship between it and
You'll need a database with the API details. You can find the database model in halfapi/models/api.sql You'll need a database with the API details. You can find the database model in halfapi/models/api.sql
With halfORM's "hop" command, we generate the models that describe the database from the database schema itself. You can find the details in [hop_api](https://gite.lirmm.fr/newsi/db/hop_api) repository.
**NOTE : The authentication module is deeply linked with [auth_lirmm](https://gite.lirmm.fr/newsi/auth_lirmm), so if you need to use the acl.connected function, you will need a running auth_lirmm server to get a token.** **NOTE : The authentication module is deeply linked with [auth_lirmm](https://gite.lirmm.fr/newsi/auth_lirmm), so if you need to use the acl.connected function, you will need a running auth_lirmm server to get a token.**
**TODO :** include a token generation tool for testing purpose. **TODO :** include a token generation tool for testing purpose.
## Dependencies ## Dependencies
- python3 - python3
- python3-pip - python3-pip
- python3-virtualenv
- python3-venv
### pip
- poetry - poetry
- uvicorn
## Installing ## Installing
As the project uses the [poetry]() package manager, you first have to install it globally. It will replace virtualenv, pip, etc... `pip install .`
As the project uses the [poetry]() package manager, you first have to install it
for the current user. Be sur to have pip binary directory ($HOME/.local/bin)
included in your PATH.
`pip3 install poetry` `pip3 install poetry`
@ -47,15 +40,6 @@ As the project uses the [poetry]() package manager, you first have to install it
Be sur to include the bin directory of pip in your PATH. Be sur to include the bin directory of pip in your PATH.
### Virtual Environment (optional)
Then, cd in the halfapi repo, and chose your python version :
`POETRY_VIRTUALENVS_PATH=$HOME/.pyvenv poetry env use 3.7`
**NOTE : The virtualenv will be automatically be activated each time you run a command with the `poetry` tool. If you want to do it the classical way, or even without virtual environment, it's up to your choice.**
Installation of the deps : Installation of the deps :

View File

@ -0,0 +1,10 @@
GUNICORN_CMD_ARGS="--daemon \
--bind unix:/var/lib/halfapi/example_api.sock \
--max-requests 200 \
--max-requests-jitter 20 \
--workers 4 \
--log-syslog-facility daemon \
--worker-class uvicorn.workers.UvicornWorker
HALFORM_CONF_DIR=/etc/half_orm
HALFAPI_CONF_DIR=/etc/half_api

View File

@ -0,0 +1,19 @@
[Unit]
Description=HalfAPI - Project : Example API Service
Requires=halfapi_example_api.socket
After=network.target
[Service]
Type=simple
User=halfapi
Group=halfapi
WorkingDirectory=/var/lib/halfapi/example_api
EnvironmentFile=/etc/default/gunicorn/halfapi_example_api
ExecStart=/usr/bin/env gunicorn halfapi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -1,10 +1,10 @@
[Unit] [Unit]
Description=uvicorn socket Description=HalfAPI - Project : Example API Socket
[Socket] [Socket]
ListenStream=/var/lib/api/lirmm_api.sock ListenStream=/var/lib/halfapi/example_api.sock
User=api User=halfapi
SocketUser=api SocketUser=halfapi
SocketGroup=www-data SocketGroup=www-data
# Optionally restrict the socket permissions even more. # Optionally restrict the socket permissions even more.
# Mode=600 # Mode=600

View File

@ -1,26 +0,0 @@
[Unit]
Description=LIRMM API daemon
Requires=lirmm_api.socket
After=network.target
[Service]
Type=simple
# the specific user that our service will run as
User=api
Group=www-data
# another option for an even more restricted service is
# DynamicUser=yes
# see http://0pointer.net/blog/dynamic-users-with-systemd.html
RuntimeDirectory=api
WorkingDirectory=/var/lib/api/halfapi
EnvironmentFile=/var/lib/api/halfapi/conf/env.merles-dev
ExecStart=/var/lib/api/.pyvenv/halfapi-MLzQW5Lp-py3.7/bin/uvicorn \
--uds /var/lib/api/lirmm_api.sock \
halfapi.app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -1 +1,48 @@
__version__ = '0.0.0' #!/usr/bin/env python3
import os
from os import environ
from configparser import ConfigParser
__version__ = '0.1.0'
print(f'HalfAPI version:{__version__}')
config = ConfigParser(defaults={
'project': {
'host': '127.0.0.1',
'port': '8000',
'secret': None,
'base_dir': None,
'production': False
}
})
config.read(filenames=['.halfapiconfig'])
PROJECT_NAME = config.get('project', 'name')
CONF_DIR = environ.get('HALFAPI_CONF_DIR', '/etc/halfapi')
config.read(filenames=[os.path.join(
CONF_DIR,
PROJECT_NAME
)])
HOST = config.get('project', 'host')
PORT = config.getint('project', 'port')
DB_NAME = f'halfapi_{PROJECT_NAME}'
with open(config.get('project', 'secret')) as secret_file:
SECRET = secret_file.read()
PRODUCTION = config.getboolean('project', 'production')
BASE_DIR = config.get('project', 'base_dir')
# DB
from half_orm.model import Model
db = Model(DB_NAME)
Domain = db.get_relation_class('api.domain')
APIRouter = db.get_relation_class('api.router')
APIRoute = db.get_relation_class('api.route')
AclFunction = db.get_relation_class('api.acl_function')
Acl = db.get_relation_class('api.acl')
RouteACL = db.get_relation_class('api.view.acl')
from halfapi.app import application

View File

@ -1,108 +1,22 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# builtins
import importlib
import sys
from os import environ
# asgi framework # asgi framework
from starlette.applications import Starlette from starlette.applications import Starlette
from starlette.authentication import UnauthenticatedUser from starlette.authentication import UnauthenticatedUser
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette.requests import Request
from starlette.responses import Response, JSONResponse from starlette.responses import Response, JSONResponse
from starlette.routing import Route from starlette.routing import Route
from starlette.types import ASGIApp
from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.authentication import AuthenticationMiddleware
# typing # typing
from typing import Any, Awaitable, Callable, MutableMapping from typing import Any, Awaitable, Callable, MutableMapping
RequestResponseEndpoint = Callable[ [Request], Awaitable[Response] ]
# hop-generated classes
from .models.api.domain import Domain
# module libraries # module libraries
from .config import CONFIG from halfapi import HOST, PORT, DB_NAME, SECRET, PRODUCTION
from .lib.jwt_middleware import JWTAuthenticationBackend
from .lib.acl_caller_middleware import AclCallerMiddleware
from .lib.responses import * from halfapi.lib.jwt_middleware import JWTAuthenticationBackend
from halfapi.lib.responses import *
def mount_domains(app: ASGIApp, domains: list): from halfapi.lib.routes import get_routes
""" Procedure to mount the registered domains on their prefixes
Parameters:
- app (ASGIApp): The Starlette instance
- domains (list): The domains to mount, retrieved from the database
with their attributes "version", "name"
Returns: Nothing
"""
for domain in domains:
if 'name' not in domain.keys() or 'version' not in domain.keys():
continue
# Retrieve domain app according to domain details
try:
print(f'Will import {domain["name"]}.app:app')
# @TODO 4-configuration
# Store domain-specific information in a configuration file
domain_mod = importlib.import_module(
f'{domain["name"]}.app')
domain_app = domain_mod.app
except ModuleNotFoundError:
sys.stderr.write(
f'Could not find module *{domain["name"]}* in sys.path\n')
continue
except ImportError:
sys.stderr.write(f'Could not import *app* from *{domain}*')
continue
except Exception as e:
sys.stderr.write(f'Error in import *{domain["name"]}*\n')
print(e)
continue
# Alter the openapi_url so the /docs page doesn't try to get
# /openapi.json (@TODO : report the bug to FastAPI)
domain_app.openapi_url = '/api/{version}/{name}/openapi.json'.format(**domain)
# Mount the domain app on the prefix
# e.g. : /v4/organigramme
try:
app.mount('/{version}/{name}'.format(**domain), app=domain_app)
except Exception as e:
print(f'Failed to mount *{domain}*\n')
def startup():
# This function is called at the instanciation of *app*
global app
# Mount the registered domains
try:
domains_list = [elt for elt in Domain().select()]
mount_domains(app, domains_list)
except Exception as e:
sys.stderr.write('Error in the *domains* retrieval\n')
raise e
if not CONFIG['HALFORM_SECRET']:
try:
CONFIG['HALFORM_SECRET'] = open('/etc/half_orm/secret').read()
print('Missing HALFORM_SECRET variable from configuration, \
read it from /etc/half_orm/secret')
except FileNotFoundError:
print('No HALFORM_SECRET variable set, and /etc/half_orm/secret \
inaccessible.')
sys.exit(1)
except PermissionError:
print("You don't have the right to read /etc/half_orm/secret")
sys.exit(1)
debug_routes = [ debug_routes = [
@ -113,22 +27,19 @@ debug_routes = [
else JSONResponse({'user':False})), else JSONResponse({'user':False})),
Route('/payload', lambda request, *args, **kwargs: Route('/payload', lambda request, *args, **kwargs:
JSONResponse({'payload':str(request.payload)})) JSONResponse({'payload':str(request.payload)}))
] if CONFIG['DEBUG'] else [] ] if not PRODUCTION else []
application = Starlette(
app = Starlette( debug=not PRODUCTION,
debug=CONFIG['DEBUG'], routes=debug_routes + get_routes(),
routes=debug_routes,
middleware=[ middleware=[
Middleware(AuthenticationMiddleware, Middleware(AuthenticationMiddleware,
backend=JWTAuthenticationBackend(secret_key=CONFIG['HALFORM_SECRET'])), backend=JWTAuthenticationBackend(secret_key=SECRET))
Middleware(AclCallerMiddleware),
], ],
exception_handlers={ exception_handlers={
401: UnauthorizedResponse, 401: UnauthorizedResponse,
404: NotFoundResponse, 404: NotFoundResponse,
500: InternalServerErrorResponse, 500: InternalServerErrorResponse,
501: NotImplementedResponse 501: NotImplementedResponse
}, }
on_startup=[startup],
) )

View File

@ -1,4 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from halfapi import (PROJECT_NAME, HOST, PORT,
PRODUCTION,
BASE_DIR,
Domain,
APIRouter,
APIRoute,
AclFunction,
Acl)
# builtins # builtins
import click import click
@ -6,22 +14,10 @@ import uvicorn
import os import os
import sys import sys
import importlib import importlib
from pprint import pprint
# database
import psycopg2
# hop-generated classes
from apidb.api.version import Version
from apidb.api.domain import Domain
from apidb.api.route import Route
from apidb.api.acl_function import AclFunction
from apidb.api.acl import Acl
HALFORM_DSN=''
HALFORM_SECRET=''
CONTEXT_SETTINGS={ CONTEXT_SETTINGS={
'default_map':{'run': {'port': 8000}} 'default_map':{'run': {}}
} }
@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS) @click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@ -30,111 +26,53 @@ def cli(ctx):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
return run() return run()
@click.option('--envfile', default=None)
@click.option('--host', default='127.0.0.1')
@click.option('--port', default='8000')
@cli.command()
def run(envfile, host, port):
local_env = {}
if envfile:
try:
with open(envfile) as f:
click.echo('Will use the following env parameters :')
local_env = dict([ tuple(line.strip().split('=', 1))
for line in f.readlines() ])
click.echo(local_env)
except FileNotFoundError:
click.echo(f'No file named {envfile}')
envfile = None
if 'DEV' in local_env.keys(): @click.option('--host', default=HOST)
debug = True @click.option('--port', default=PORT)
reload = True @cli.command()
log_level = 'debug' def run(host, port):
else: debug = reload = not PRODUCTION
reload = False log_level = 'info' if PRODUCTION else 'debug'
log_level = 'info'
click.echo('Launching application') click.echo('Launching application')
sys.path.insert(0, os.getcwd()) sys.path.insert(0, BASE_DIR)
click.echo(f'current python_path : {sys.path}') click.echo(f'current python_path : {sys.path}')
uvicorn.run('halfapi.app:app', uvicorn.run('halfapi.app:app',
env_file=envfile,
host=host, host=host,
port=int(port), port=int(port),
log_level=log_level, log_level=log_level,
reload=reload) reload=reload)
@click.option('--dbname', default='api')
@click.option('--host', default='127.0.0.1')
@click.option('--port', default=5432)
@click.option('--apihost', default='127.0.0.1')
@click.option('--apiport', default=8080)
@click.option('--user', default='api')
@click.option('--password', default='')
@click.option('--domain', default='organigramme')
@click.option('--drop', is_flag=True, default=False)
@cli.command()
def dbupdate(dbname, host, port, apihost, apiport, user, password, domain, drop):
def dropdb(): def delete_domain(domain):
if not click.confirm(f'will now drop database {dbname}', default=True):
return False
conn = psycopg2.connect({
'dbname': dbname,
'host': host,
'port': port,
'user': user,
'password': password
})
cur = conn.cursor()
cur.execute(f'drop database {dbname};')
conn.commit()
cur.close()
conn.close()
return True
def delete_domain():
d = Domain(name=domain) d = Domain(name=domain)
if len(d) < 1: if len(d) != 1:
return False return False
acl = Acl(domain=domain) d.delete(delete_all=True)
acl.delete()
fct = AclFunction(domain=domain)
fct.delete()
route = Route(domain=domain)
route.delete()
d.delete()
return True return True
@click.option('--domain', default=None)
@cli.command()
def dbupdate(domain):
def add_acl_fct(fct): def add_acl_fct(fct):
acl = AclFunction() acl = AclFunction()
acl.version = version
acl.domain = domain acl.domain = domain
acl.name = fct.__name__ acl.name = fct.__name__
if len(acl) == 0: if len(acl) == 0:
acl.insert() acl.insert()
def add_acl(name, **kwargs):
acl = Acl() def add_acls(acls, **route):
acl.version = version route.pop('fct_name')
acl.domain = domain acl = Acl(**route)
acl.name = name
acl.path = kwargs['path'] for fct in acls:
acl.http_verb = kwargs['verb'] acl.acl_fct_name = fct.__name__
for fct in kwargs['acl']:
acl.function = fct.__name__
if len(acl) == 0: if len(acl) == 0:
if fct is not None: if fct is not None:
@ -146,38 +84,55 @@ def dbupdate(dbname, host, port, apihost, apiport, user, password, domain, drop)
acl.delete() acl.delete()
def add_route(name, **kwargs): def get_fct_name(http_verb, path):
click.echo(f'Adding route {version}/{domain}/{name}') if path[0] != '/':
route = Route() raise Exception('Malformed path')
route.version = version
elts = path[1:].split('/')
fct_name = [http_verb.lower()]
for elt in elts:
if elt[0] == '{':
fct_name.append(elt[1:-1].split(':')[0].upper())
else:
fct_name.append(elt)
return '_'.join(fct_name)
def add_router(name):
router = APIRouter()
router.name = name
router.domain = domain
if len(router) == 0:
router.insert()
def add_route(http_verb, path, router, acls):
click.echo(f'Adding route /{domain}/{router}{path}')
route = APIRoute()
route.http_verb = http_verb
route.path = path
route.fct_name = get_fct_name(http_verb, path)
route.router = router
route.domain = domain route.domain = domain
route.path = kwargs['path']
if len(route) == 0: if len(route) == 0:
route.insert() route.insert()
def add_routes_and_acl(routes): add_acls(acls, **route.to_dict())
for name, route_params in routes.items():
add_route(name, **route_params)
add_acl(name, **route_params)
def add_domain(): def add_domain():
new_version = Version(name=version, server=apihost, port=apiport)
if len(new_version) == 0:
click.echo(f'New version : {version}')
new_version.insert()
new_domain = Domain(name=domain) new_domain = Domain(name=domain)
new_domain.version = version
if len(new_domain) == 0: if len(new_domain) == 0:
click.echo(f'New domain {domain}') click.echo(f'New domain {domain}')
new_domain.insert() new_domain.insert()
sys.path.insert(0, BASE_DIR)
if drop: delete_domain(domain)
dropdb()
delete_domain()
acl_set = set() acl_set = set()
@ -186,19 +141,20 @@ def dbupdate(dbname, host, port, apihost, apiport, user, password, domain, drop)
# module retrieval # module retrieval
dom_mod = importlib.import_module(domain) dom_mod = importlib.import_module(domain)
version = dom_mod.API_VERSION
add_domain() add_domain()
# add main routes
ROUTES = dom_mod.ROUTES
add_routes_and_acl(dom_mod.ROUTES)
# add sub routers # add sub routers
ROUTERS = dom_mod.ROUTERS ROUTERS = dom_mod.ROUTERS
for router_name in dom_mod.ROUTERS: for router_name in dom_mod.ROUTERS:
router_mod = importlib.import_module(f'.routers.{router_name}', domain) router_mod = importlib.import_module(f'.routers.{router_name}', domain)
add_routes_and_acl(router_mod.ROUTES) add_router(router_name)
pprint(router_mod.ROUTES)
for route_path, route_params in router_mod.ROUTES.items():
for http_verb, acls in route_params.items():
add_route(http_verb, route_path, router_name, acls)
except ImportError: except ImportError:
click.echo(f'The domain {domain} has no *ROUTES* variable', err=True) click.echo(f'The domain {domain} has no *ROUTES* variable', err=True)
@ -206,9 +162,5 @@ def dbupdate(dbname, host, port, apihost, apiport, user, password, domain, drop)
click.echo(e, err=True) click.echo(e, err=True)
if __name__ == '__main__': if __name__ == '__main__':
cli() cli()

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python3
from os import environ
# Configuration
CONFIG={}
CONFIG['DEBUG'] = environ.get('DEBUG', False)
CONFIG['DEBUG_ACL'] = environ.get('DEBUG_ACL', False)
CONFIG['HALFORM_SECRET'] = environ.get('HALFORM_SECRET', False)

View File

@ -1,159 +0,0 @@
#!/usr/bin/env python3
from os import environ
from starlette.exceptions import HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.routing import Match, Mount
from starlette.types import ASGIApp, Receive, Scope, Send
from halfapi.config import CONFIG
from halfapi.models.api.view.acl import Acl as AclView
class DebugRouteException(Exception):
def __init__(self, *args, **kwargs):
super().__init__(self)
def match_route(app: ASGIApp, scope: Scope):
""" Checks all routes from "app" and checks if it matches with the one from
scope
Parameters:
- app (ASGIApp): The Starlette instance
- scope (MutableMapping[str, Any]): The requests scope
Returns:
- (dict, dict): The first dict of the tuple is the details on the
route, the second one is the path parameters
Raises:
HTTPException
"""
""" The *result* variable is fitted to the filter that will be applied when
searching the route in the database.
Refer to the database documentation for more details on the api.route
table.
"""
result = {
'domain': None,
'name': None,
'http_verb': None,
'version': None
}
if 'DEBUG' in CONFIG.keys() and len(scope['path'].split('/')) <= 3:
raise DebugRouteException()
try:
""" Identification of the parts of the path
Examples :
version : v4
domain : organigramme
path : laboratoire/personnel
"""
_, result['domain'], path = scope['path'].split('/', 2)
except ValueError as e:
#404 Not found
raise HTTPException(404)
# Prefix the path with "/"
path = f'/{path}'
for route in app.routes:
if type(route) != Mount:
""" The root app should not have exposed routes,
only the mounted domains have some.
"""
continue
""" Clone the scope to assign the path to the path without the
matching domain, be careful to the "root_path" of the mounted domain.
@TODO
Also, improper array unpacking may make crash the program without any
explicit error, we may have to improve this as we only rely on this
function to accomplish all the routing
"""
subscope = scope.copy()
_, result['domain'], subpath = path.split('/', 2)
subscope['path'] = f'/{subpath}'
for mount_route in route.routes:
# Parse all domain routes
submatch = mount_route.matches(subscope)
if submatch[0] != Match.FULL:
continue
# Route matches
try:
result['name'] = submatch[1]['endpoint'].__name__
result['http_verb'] = scope['method']
except Exception as e:
print(e)
return result, submatch[1]['path_params']
raise HTTPException(404)
class AclCallerMiddleware(BaseHTTPMiddleware):
async def __call__(self, scope:Scope, receive: Receive, send: Send) -> None:
""" Points out to the domain which ACL function it should call
Parameters :
- request (Request): The current request
- call_next (RequestResponseEndpoint): The next middleware/route function
Return:
Response
"""
print('Hit AclCallerMiddleware of API')
if scope['type'] != 'http':
await self.app(scope, receive, send)
return
app = self.app
while True:
if not hasattr(app, 'app'):
break
app = app.app
if scope['path'].split('/')[-1] not in ['docs','openapi.json','redoc']:
# routes in the the database, the others being
# docs/openapi.json/redoc
try:
d_match, path_params = match_route(app, scope)
scope['acls'] = []
for acl in AclView(**d_match).select():
# retrieve related ACLs
if ('acl_function_name' not in acl.keys()
or 'domain' not in acl.keys()):
continue
scope['acls'].append(acl['acl_function_name'])
except StopIteration:
# TODO : No ACL sur une route existante, prevenir l'admin?
print("No ACL")
pass
except DebugRouteException:
print("Debug route")
if 'DEBUG_ACL' in environ.keys():
scope['acls'] = environ['DEBUG_ACL'].split(':')
else:
scope['acls'] = []
elif CONFIG['DEBUG']:
scope['dev_route'] = True
res = await self.app(scope, receive, send)

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python3
from starlette.requests import Request
from starlette.exceptions import HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
class AclMiddleware(BaseHTTPMiddleware):
def __init__(self, app, acl_module):
super().__init__(app)
self.acl_module = acl_module
async def dispatch(self, request: Request, call_next):
""" Checks the "acls" key in the scope and applies the
corresponding functions in the current module's acl lib.
Raises an exception if no acl function returns True
"""
print(f'Hit acl {__name__} middleware')
if 'dev_route' in request.scope.keys():
print('[DEBUG] Dev route, no ACL')
return await call_next(request)
if not('acls' in request.scope.keys()
and type(request.scope['acls']) == list):
print('BUG : scope["acls"] does not exist or is not a list')
raise HTTPException(500)
for acl_fct_name in request.scope['acls']:
print(f'Will apply {acl_fct_name}')
try:
fct = getattr(self.acl_module, acl_fct_name)
if fct(request) is True:
return await call_next(request)
except AttributeError as e:
print(f'No ACL function "{acl_fct_name}" in {__name__} module')
print(e)
break
except Exception as e:
print(e)
raise HTTPException(500)
raise HTTPException(401)

74
halfapi/lib/routes.py Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python3
from functools import wraps
import importlib
import sys
from halfapi import (PROJECT_NAME, HOST, PORT,
PRODUCTION,
Domain,
APIRouter,
APIRoute,
AclFunction,
Acl)
from halfapi.lib.responses import *
from starlette.exceptions import HTTPException
from starlette.routing import Mount, Route
from starlette.requests import Request
def get_routes(domains=None):
""" Procedure to mount the registered domains on their prefixes
Parameters:
- app (ASGIApp): The Starlette instance
- domains (list): The domains to mount, retrieved from the database
with their attributes "name"
Returns: Nothing
"""
def route_decorator(fct, acls_mod, acls):
@wraps(fct)
def caller(req: Request, *args, **kwargs):
for acl_fct_name in acls:
acl_fct = getattr(acls_mod, acl_fct_name)
if acl_fct(req, *args, **kwargs):
return func(req, *args, **kwargs)
raise HTTPException(401)
return caller
app_routes = []
for domain in Domain(name=domains).select():
domain_acl_mod = importlib.import_module(f'{domain["name"]}.acl')
domain_routes = []
for router in APIRouter(domain=domain['name']).select():
router_routes = []
router_mod = importlib.import_module(
'{domain}.routers.{name}'.format(**router))
with APIRoute(domain=domain['name'],
router=router['name']) as routes:
for route in routes.select():
fct_name = route.pop('fct_name')
acls = [ list(elt.values()).pop()
for elt in Acl(**route).select('acl_fct_name') ]
router_routes.append(
Route(route['path'],
route_decorator(
getattr(router_mod, fct_name),
domain_acl_mod,
acls
), methods=[route['http_verb']])
)
domain_routes.append(
Mount('/{name}'.format(**router), routes=router_routes))
app_routes.append(Mount('/{name}'.format(**domain),
routes=domain_routes))
return app_routes

View File

@ -1,11 +0,0 @@
"""This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
"""
__all__ = [
'api',
'db_connector'
]

View File

@ -2,72 +2,73 @@ create schema api;
create type verb as enum ('POST', 'GET', 'PUT', 'DELETE'); create type verb as enum ('POST', 'GET', 'PUT', 'DELETE');
create table api.version ( create table api.domain (
name text primary key, name text,
server cidr not null default '127.0.0.1', primary key (name)
port integer not null
); );
create table api.domain ( create table api.router (
version text references api.version(name),
name text, name text,
primary key (version, name) domain text,
primary key (name, domain)
); );
alter table api.router add constraint router_domain_fkey foreign key (domain) references api.domain(name) on update cascade on delete cascade;
create table api.route ( create table api.route (
path text, -- relative to /api/<version>/<domain> http_verb verb,
version text, path text, -- relative to /<domain>/<router>
fct_name text,
router text,
domain text, domain text,
primary key (path, domain, version) primary key (http_verb, path, router, domain)
); );
alter table api.route add constraint route_domain_fkey foreign key (version, domain) references api.domain(version, name) on update cascade on delete cascade; alter table api.route add constraint route_router_fkey foreign key (router, domain) references api.router(name, domain) on update cascade on delete cascade;
create table api.acl_function ( create table api.acl_function (
name text, name text,
description text, description text,
version text,
domain text, domain text,
primary key (name, version, domain) primary key (name, domain)
); );
alter table api.acl_function add constraint acl_function_domain_fkey foreign key (version, domain) references api.domain(version, name) on update cascade on delete cascade; alter table api.acl_function add constraint acl_function_domain_fkey foreign key (domain) references api.domain(name) on update cascade on delete cascade;
create table api.acl ( create table api.acl (
name text,
http_verb verb, http_verb verb,
path text not null, path text not null,
version text, router text,
domain text not null, domain text,
function text not null, acl_fct_name text,
primary key (name, version, domain, function) primary key (http_verb, path, router, domain, acl_fct_name)
); );
alter table api.acl add constraint acl_route_fkey foreign key (path, version, domain) references api.route(path, version, domain) on update cascade on delete cascade; alter table api.acl add constraint acl_route_fkey foreign key (http_verb, path,
alter table api.acl add constraint acl_function_fkey foreign key (function, version, domain) references api.acl_function(name, version, domain) on update cascade on delete cascade; router, domain) references api.route(http_verb, path, router, domain) on update cascade on delete cascade;
alter table api.acl add constraint acl_function_fkey foreign key (acl_fct_name, domain) references api.acl_function(name, domain) on update cascade on delete cascade;
create schema "api.view"; create schema "api.view";
create view "api.view".route as create view "api.view".route as
select select
route.*, route.*,
version.name, '/'::text || route.domain || '/'::text || route.router || route.path AS abs_path
version.server,
version.port,
'/'::text || route.domain || route.path AS abs_path
from from
api.route api.route
join api.domain on join api.domain on
route.domain = domain.name route.domain = domain.name
join api.version on ;
domain.version = version.name;
create view "api.view".acl as create view "api.view".acl as
select select
acl.*, acl.*,
acl_function.name as acl_function_name, '/'::text || route.domain || '/'::text || route.router || route.path AS abs_path
'/'::text || acl.domain || acl.path AS abs_path
from from
api.acl api.acl
join api.acl_function on join api.acl_function on
acl.function = acl_function.name; acl.acl_fct_name = acl_function.name
join api.route on
acl.domain = route.domain
and acl.router = route.router
and acl.path = route.path;

View File

@ -1,15 +0,0 @@
"""This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
"""
__all__ = [
'acl',
'acl_function',
'domain',
'route',
'version',
'view'
]

View File

@ -1,52 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.acl povides the Acl class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ..db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.acl')
class Acl( __RCLS):
"""
__RCLS: <class 'half_orm.relation.Table_ApiApiAcl'>
This class allows you to manipulate the data in the PG relation:
TABLE: "api"."api"."acl"
FIELDS:
- name: (text) PK
- http_verb: (verb)
- path: (text) NOT NULL
- version: (text) PK
- domain: (text) PK
- function: (text) PK
FOREIGN KEYS:
- acl_route_fkey: (path, version, domain)
"api"."api"."route"(path, version, domain)
- acl_function_fkey: (function, version, domain)
"api"."api"."acl_function"(name, version, domain)
"""
def __init__(self, **kwargs):
super(Acl, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,50 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.acl_function povides the AclFunction class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ..db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.acl_function')
class AclFunction( __RCLS):
"""
__RCLS: <class 'half_orm.relation.Table_ApiApiAcl_function'>
This class allows you to manipulate the data in the PG relation:
TABLE: "api"."api"."acl_function"
FIELDS:
- name: (text) PK
- description: (text)
- version: (text) PK
- domain: (text) PK
FOREIGN KEYS:
- _reverse_fkey_api_api_acl_function_version_domain: (name, version, domain)
"api"."api"."acl"(function, version, domain)
- acl_function_domain_fkey: (version, domain)
"api"."api"."domain"(version, name)
"""
def __init__(self, **kwargs):
super(AclFunction, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,50 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.domain povides the Domain class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ..db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.domain')
class Domain( __RCLS):
"""
__RCLS: <class 'half_orm.relation.Table_ApiApiDomain'>
This class allows you to manipulate the data in the PG relation:
TABLE: "api"."api"."domain"
FIELDS:
- version: (text) PK
- name: (text) PK
FOREIGN KEYS:
- _reverse_fkey_api_api_acl_function_version_domain: (version, name)
"api"."api"."acl_function"(version, domain)
- domain_version_fkey: (version)
"api"."api"."version"(name)
- _reverse_fkey_api_api_route_version_domain: (version, name)
"api"."api"."route"(version, domain)
"""
def __init__(self, **kwargs):
super(Domain, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,49 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.route povides the Route class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ..db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.route')
class Route( __RCLS):
"""
__RCLS: <class 'half_orm.relation.Table_ApiApiRoute'>
This class allows you to manipulate the data in the PG relation:
TABLE: "api"."api"."route"
FIELDS:
- path: (text) PK
- version: (text) PK
- domain: (text) PK
FOREIGN KEYS:
- _reverse_fkey_api_api_acl_path_version_domain: (path, version, domain)
"api"."api"."acl"(path, version, domain)
- route_domain_fkey: (version, domain)
"api"."api"."domain"(version, name)
"""
def __init__(self, **kwargs):
super(Route, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,47 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.version povides the Version class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ..db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.version')
class Version( __RCLS):
"""
__RCLS: <class 'half_orm.relation.Table_ApiApiVersion'>
This class allows you to manipulate the data in the PG relation:
TABLE: "api"."api"."version"
FIELDS:
- name: (text) PK
- server: (cidr) NOT NULL
- port: (int4) NOT NULL
FOREIGN KEY:
- _reverse_fkey_api_api_domain_version: (name)
"api"."api"."domain"(version)
"""
def __init__(self, **kwargs):
super(Version, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,11 +0,0 @@
"""This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
"""
__all__ = [
'acl',
'route'
]

View File

@ -1,49 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.view.acl povides the Acl class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ...db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.view.acl')
class Acl( __RCLS):
"""
__RCLS: <class 'half_orm.relation.View_ApiApiviewAcl'>
This class allows you to manipulate the data in the PG relation:
VIEW: "api"."api.view"."acl"
FIELDS:
- name: (text)
- http_verb: (verb)
- path: (text)
- version: (text)
- domain: (text)
- function: (text)
- acl_function_name: (text)
- abs_path: (text)
"""
def __init__(self, **kwargs):
super(Acl, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,48 +0,0 @@
#-*- coding: utf-8 -*-
# pylint: disable=wrong-import-order
"""The module apidb.api.view.route povides the Route class.
WARNING!
This file is part of the apidb package. It has been generated by the
command halfORM. To keep it in sync with your database structure, just rerun
halfORM.
More information on the half_orm library on https://github.com/collorg/halfORM.
DO NOT REMOVE OR MODIFY THE LINES BEGINING WITH:
#>>> PLACE YOUR CODE BELOW...
#<<< PLACE YOUR CODE ABOVE...
MAKE SURE YOU PLACE YOUR CODE BETWEEN THESE LINES OR AT THE END OF THE FILE.
halfORM ONLY PRESERVES THE CODE BETWEEN THESE MARKS WHEN IT IS RUN.
"""
from ...db_connector import base_relation_class
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!
#<<< PLACE YOUR CODE ABOVE THIS LINE. DO NOT REMOVE THIS LINE!
__RCLS = base_relation_class('api.view.route')
class Route( __RCLS):
"""
__RCLS: <class 'half_orm.relation.View_ApiApiviewRoute'>
This class allows you to manipulate the data in the PG relation:
VIEW: "api"."api.view"."route"
FIELDS:
- path: (text)
- version: (text)
- domain: (text)
- name: (text)
- server: (cidr)
- port: (int4)
- abs_path: (text)
"""
def __init__(self, **kwargs):
super(Route, self).__init__(**kwargs)
#>>> PLACE YOUR CODE BELLOW THIS LINE. DO NOT REMOVE THIS LINE!

View File

@ -1,17 +0,0 @@
#-*- coding: utf-8 -*-
"""This module exports the fonction base_relation_class which is
imported by all modules in the package apidb.
"""
from half_orm.model import Model
__all__ = ['base_relation_class']
MODEL = Model('api', scope=__name__)
def base_relation_class(qrn):
"""Returns the class corresponding to the QRN (qualified relation name).
"""
cls = MODEL.get_relation_class(qrn)
return cls

848
poetry.lock generated
View File

@ -1,848 +0,0 @@
[[package]]
category = "main"
description = "Package for api PG (apidb)"
name = "apidb"
optional = false
python-versions = "*"
version = "0.0.6"
[package.dependencies]
half_orm = "*"
[package.source]
reference = "616cef51b8aa5b5c07a05c9c47c9a6ae9656ffa1"
type = "git"
url = "git@gite.lirmm.fr:newsi/db/hop_api.git"
[[package]]
category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
[package.extras]
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
docs = ["sphinx", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]]
category = "dev"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi"
optional = false
python-versions = "*"
version = "2020.6.20"
[[package]]
category = "dev"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet"
optional = false
python-versions = "*"
version = "3.0.4"
[[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.2"
[[package]]
category = "dev"
description = "Cross-platform colored terminal text."
marker = "sys_platform == \"win32\""
name = "colorama"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "main"
description = "XML bomb protection for Python stdlib modules"
name = "defusedxml"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.6.0"
[[package]]
category = "main"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
name = "fastapi"
optional = true
python-versions = ">=3.6"
version = "0.59.0"
[package.dependencies]
pydantic = ">=0.32.2,<2.0.0"
starlette = "0.13.4"
[package.extras]
all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn (>=0.11.5,<0.12.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"]
dev = ["python-jose (>=3.1.0,<4.0.0)", "passlib (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn (>=0.11.5,<0.12.0)", "graphene (>=2.1.8,<3.0.0)"]
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer (>=0.3.0,<0.4.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"]
test = ["pytest (5.4.3)", "pytest-cov (2.10.0)", "mypy (0.782)", "black (19.10b0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"]
[[package]]
category = "main"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
name = "h11"
optional = false
python-versions = "*"
version = "0.9.0"
[[package]]
category = "main"
description = "A simple ORM in Python only dealing with the DML part of SQL."
name = "half-orm"
optional = false
python-versions = "*"
version = "0.2.0"
[package.dependencies]
PyYAML = "*"
psycopg2-binary = "*"
[package.source]
reference = "fe53195abb637d2192857e8b4878f4865b0fcce4"
type = "git"
url = "git@gite.lirmm.fr:newsi/halfORM.git"
[[package]]
category = "main"
description = "A collection of framework independent HTTP protocol utils."
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
name = "httptools"
optional = false
python-versions = "*"
version = "0.1.1"
[package.extras]
test = ["Cython (0.29.14)"]
[[package]]
category = "dev"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.10"
[[package]]
category = "dev"
description = "Read metadata from Python packages"
marker = "python_version < \"3.8\""
name = "importlib-metadata"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.7.0"
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "rst.linker"]
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]]
category = "main"
description = "Load me later. A lazy plugin management system."
name = "lml"
optional = true
python-versions = "*"
version = "0.0.9"
[[package]]
category = "main"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
name = "lxml"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
version = "4.5.2"
[package.extras]
cssselect = ["cssselect (>=0.7)"]
html5 = ["html5lib"]
htmlsoup = ["beautifulsoup4"]
source = ["Cython (>=0.29.7)"]
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.5"
version = "8.4.0"
[[package]]
category = "main"
description = "Python API and tools to manipulate OpenDocument files"
name = "odfpy"
optional = true
python-versions = "*"
version = "1.4.1"
[package.dependencies]
defusedxml = "*"
[[package]]
category = "main"
description = ""
name = "organigramme"
optional = true
python-versions = "^3.7"
version = "0.1.0"
[package.dependencies]
fastapi = "^0"
half-orm = "branch master"
halfapi = "branch master"
pyexcel = "^0.6.2"
pyexcel-ods = "^0.5.6"
pyexcel-ods3 = "^0.5.3"
python-dotenv = "^0.14.0"
sidb = "branch master"
starlette = "^0"
uvicorn = "^0"
[package.source]
reference = "41984a0f62ee0a951ae06647601dd4a7091c5299"
type = "git"
url = "git@gite.lirmm.fr:newsi/api/organigramme.git"
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.4"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.dependencies]
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
category = "main"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
name = "psycopg2-binary"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "2.8.5"
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
name = "py"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.9.0"
[[package]]
category = "main"
description = "Data validation and settings management using python 3.6 type hinting"
name = "pydantic"
optional = true
python-versions = ">=3.6"
version = "1.6.1"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
typing_extensions = ["typing-extensions (>=3.7.2)"]
[[package]]
category = "main"
description = "A wrapper library that provides one API to read, manipulate and writedata in different excel formats"
name = "pyexcel"
optional = true
python-versions = ">=3.6"
version = "0.6.2"
[package.dependencies]
lml = ">=0.0.4"
pyexcel-io = ">=0.5.19"
texttable = ">=0.8.2"
[package.extras]
ods = ["pyexcel-ods3 (>=0.5.0)"]
xls = ["pyexcel-xls (>=0.5.0)"]
xlsx = ["pyexcel-xlsx (>=0.5.0)"]
[[package]]
category = "main"
description = "A Python package to create/manipulate OpenDocumentFormat files"
name = "pyexcel-ezodf"
optional = true
python-versions = "*"
version = "0.3.4"
[package.dependencies]
lxml = "*"
[[package]]
category = "main"
description = "A python library to read and write structured data in csv, zipped csvformat and to/from databases"
name = "pyexcel-io"
optional = true
python-versions = "*"
version = "0.5.20"
[package.dependencies]
lml = ">=0.0.4"
[package.extras]
ods = ["pyexcel-ods3 (>=0.5.0)"]
xls = ["pyexcel-xls (>=0.5.0)"]
xlsx = ["pyexcel-xlsx (>=0.5.0)"]
[[package]]
category = "main"
description = "A wrapper library to read, manipulate and write data in ods format"
name = "pyexcel-ods"
optional = true
python-versions = "*"
version = "0.5.6"
[package.dependencies]
odfpy = ">=1.3.5"
pyexcel-io = ">=0.5.16"
[[package]]
category = "main"
description = "A wrapper library to read, manipulate and write data in ods format"
name = "pyexcel-ods3"
optional = true
python-versions = "*"
version = "0.5.3"
[package.dependencies]
lxml = "*"
pyexcel-ezodf = ">=0.3.3"
pyexcel-io = ">=0.5.10"
[[package]]
category = "main"
description = "JSON Web Token implementation in Python"
name = "pyjwt"
optional = false
python-versions = "*"
version = "1.7.1"
[package.extras]
crypto = ["cryptography (>=1.4)"]
flake8 = ["flake8", "flake8-import-order", "pep8-naming"]
test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"]
[[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "5.4.3"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.5.0"
wcwidth = "*"
[package.dependencies.importlib-metadata]
python = "<3.8"
version = ">=0.12"
[package.extras]
checkqa-mypy = ["mypy (v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
category = "dev"
description = "py.test plugin that allows you to add environment variables."
name = "pytest-env"
optional = false
python-versions = "*"
version = "0.6.2"
[package.dependencies]
pytest = ">=2.6.0"
[[package]]
category = "main"
description = "Add .env support to your django/flask apps in development and deployments"
name = "python-dotenv"
optional = false
python-versions = "*"
version = "0.14.0"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
category = "main"
description = "YAML parser and emitter for Python"
name = "pyyaml"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "5.3.1"
[[package]]
category = "dev"
description = "Python HTTP for Humans."
name = "requests"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.24.0"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<4"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
category = "main"
description = "Package for si PG"
name = "sidb"
optional = true
python-versions = "*"
version = "0.0.0"
[package.dependencies]
half_orm = "*"
[package.source]
reference = "d0f14a9631eecd29098d13a3f34e9cd533145f24"
type = "git"
url = "git@gite.lirmm.fr:newsi/sidb_halfORM.git"
[[package]]
category = "dev"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.15.0"
[[package]]
category = "main"
description = "The little ASGI library that shines."
name = "starlette"
optional = false
python-versions = ">=3.6"
version = "0.13.4"
[package.extras]
full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
[[package]]
category = "main"
description = "module for creating simple ASCII tables"
name = "texttable"
optional = true
python-versions = "*"
version = "1.6.2"
[[package]]
category = "dev"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.25.9"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
[[package]]
category = "main"
description = "The lightning-fast ASGI server."
name = "uvicorn"
optional = false
python-versions = "*"
version = "0.11.6"
[package.dependencies]
click = ">=7.0.0,<8.0.0"
h11 = ">=0.8,<0.10"
httptools = ">=0.1.0,<0.2.0"
uvloop = ">=0.14.0"
websockets = ">=8.0.0,<9.0.0"
[package.extras]
watchgodreload = ["watchgod (>=0.6,<0.7)"]
[[package]]
category = "main"
description = "Fast implementation of asyncio event loop on top of libuv"
marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
name = "uvloop"
optional = false
python-versions = "*"
version = "0.14.0"
[[package]]
category = "dev"
description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.2.5"
[[package]]
category = "main"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
name = "websockets"
optional = false
python-versions = ">=3.6.1"
version = "8.1"
[[package]]
category = "dev"
description = "Backport of pathlib-compatible object wrapper for zip files"
marker = "python_version < \"3.8\""
name = "zipp"
optional = false
python-versions = ">=3.6"
version = "3.1.0"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[extras]
organigramme = ["fastapi", "organigramme", "sidb", "pyexcel-ods3"]
[metadata]
content-hash = "826725e53dedda2a3acbf7f004594358246da3c67c5006586c67e408fb41bee7"
python-versions = "^3.7"
[metadata.files]
apidb = []
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
]
certifi = [
{file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"},
{file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
]
chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
]
defusedxml = [
{file = "defusedxml-0.6.0-py2.py3-none-any.whl", hash = "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93"},
{file = "defusedxml-0.6.0.tar.gz", hash = "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"},
]
fastapi = [
{file = "fastapi-0.59.0-py3-none-any.whl", hash = "sha256:50b58aa3e7d5bcb4a4404ac7e550cc53f0cf7ca0fd13c7fd515693dc23c9caef"},
{file = "fastapi-0.59.0.tar.gz", hash = "sha256:c04dacd3deed0fd0ffdcdb116b5a04ffa257656885be7fae7234f4f62ec4a0a9"},
]
h11 = [
{file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
]
half-orm = []
httptools = [
{file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"},
{file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"},
{file = "httptools-0.1.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6"},
{file = "httptools-0.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c"},
{file = "httptools-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a"},
{file = "httptools-0.1.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f"},
{file = "httptools-0.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2"},
{file = "httptools-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009"},
{file = "httptools-0.1.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"},
{file = "httptools-0.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d"},
{file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"},
{file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"},
]
idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
]
importlib-metadata = [
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
]
lml = [
{file = "lml-0.0.9-py2.py3-none-any.whl", hash = "sha256:b1bef669dc077a1075fa64b99229b6341085b3b3a98d29c66df1853cc14e6c1a"},
{file = "lml-0.0.9.tar.gz", hash = "sha256:ea5ba817b4adc9e9f5c21725cd2475f912933b7e2dfdf0792aed80077154f63f"},
]
lxml = [
{file = "lxml-4.5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20"},
{file = "lxml-4.5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef"},
{file = "lxml-4.5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5"},
{file = "lxml-4.5.2-cp27-cp27m-win32.whl", hash = "sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a"},
{file = "lxml-4.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f"},
{file = "lxml-4.5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6"},
{file = "lxml-4.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443"},
{file = "lxml-4.5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0"},
{file = "lxml-4.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1"},
{file = "lxml-4.5.2-cp35-cp35m-win32.whl", hash = "sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88"},
{file = "lxml-4.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730"},
{file = "lxml-4.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1"},
{file = "lxml-4.5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe"},
{file = "lxml-4.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258"},
{file = "lxml-4.5.2-cp36-cp36m-win32.whl", hash = "sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae"},
{file = "lxml-4.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481"},
{file = "lxml-4.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba"},
{file = "lxml-4.5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd"},
{file = "lxml-4.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed"},
{file = "lxml-4.5.2-cp37-cp37m-win32.whl", hash = "sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e"},
{file = "lxml-4.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a"},
{file = "lxml-4.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d"},
{file = "lxml-4.5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7"},
{file = "lxml-4.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843"},
{file = "lxml-4.5.2-cp38-cp38-win32.whl", hash = "sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f"},
{file = "lxml-4.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee"},
{file = "lxml-4.5.2.tar.gz", hash = "sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6"},
]
more-itertools = [
{file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
{file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
]
odfpy = [
{file = "odfpy-1.4.1-py2.7.egg", hash = "sha256:fc3b8d1bc098eba4a0fda865a76d9d1e577c4ceec771426bcb169a82c5e9dfe0"},
{file = "odfpy-1.4.1.tar.gz", hash = "sha256:db766a6e59c5103212f3cc92ec8dd50a0f3a02790233ed0b52148b70d3c438ec"},
]
organigramme = []
packaging = [
{file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
psycopg2-binary = [
{file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5"},
{file = "psycopg2_binary-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac"},
{file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38"},
{file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389"},
{file = "psycopg2_binary-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9"},
{file = "psycopg2_binary-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04"},
{file = "psycopg2_binary-2.8.5-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3"},
{file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057"},
{file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce"},
{file = "psycopg2_binary-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4"},
{file = "psycopg2_binary-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb"},
{file = "psycopg2_binary-2.8.5-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434"},
{file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98"},
{file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d"},
{file = "psycopg2_binary-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1"},
{file = "psycopg2_binary-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162"},
{file = "psycopg2_binary-2.8.5-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4"},
{file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab"},
{file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505"},
{file = "psycopg2_binary-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3"},
{file = "psycopg2_binary-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e"},
{file = "psycopg2_binary-2.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a"},
{file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266"},
{file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522"},
{file = "psycopg2_binary-2.8.5-cp38-cp38-win32.whl", hash = "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa"},
{file = "psycopg2_binary-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"},
]
py = [
{file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
]
pydantic = [
{file = "pydantic-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:418b84654b60e44c0cdd5384294b0e4bc1ebf42d6e873819424f3b78b8690614"},
{file = "pydantic-1.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4900b8820b687c9a3ed753684337979574df20e6ebe4227381d04b3c3c628f99"},
{file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b49c86aecde15cde33835d5d6360e55f5e0067bb7143a8303bf03b872935c75b"},
{file = "pydantic-1.6.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2de562a456c4ecdc80cf1a8c3e70c666625f7d02d89a6174ecf63754c734592e"},
{file = "pydantic-1.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f769141ab0abfadf3305d4fcf36660e5cf568a666dd3efab7c3d4782f70946b1"},
{file = "pydantic-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dc946b07cf24bee4737ced0ae77e2ea6bc97489ba5a035b603bd1b40ad81f7e"},
{file = "pydantic-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:36dbf6f1be212ab37b5fda07667461a9219c956181aa5570a00edfb0acdfe4a1"},
{file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:1783c1d927f9e1366e0e0609ae324039b2479a1a282a98ed6a6836c9ed02002c"},
{file = "pydantic-1.6.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cf3933c98cb5e808b62fae509f74f209730b180b1e3c3954ee3f7949e083a7df"},
{file = "pydantic-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f8af9b840a9074e08c0e6dc93101de84ba95df89b267bf7151d74c553d66833b"},
{file = "pydantic-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:40d765fa2d31d5be8e29c1794657ad46f5ee583a565c83cea56630d3ae5878b9"},
{file = "pydantic-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3fa799f3cfff3e5f536cbd389368fc96a44bb30308f258c94ee76b73bd60531d"},
{file = "pydantic-1.6.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6c3f162ba175678218629f446a947e3356415b6b09122dcb364e58c442c645a7"},
{file = "pydantic-1.6.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:eb75dc1809875d5738df14b6566ccf9fd9c0bcde4f36b72870f318f16b9f5c20"},
{file = "pydantic-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:530d7222a2786a97bc59ee0e0ebbe23728f82974b1f1ad9a11cd966143410633"},
{file = "pydantic-1.6.1-py36.py37.py38-none-any.whl", hash = "sha256:b5b3489cb303d0f41ad4a7390cf606a5f2c7a94dcba20c051cd1c653694cb14d"},
{file = "pydantic-1.6.1.tar.gz", hash = "sha256:54122a8ed6b75fe1dd80797f8251ad2063ea348a03b77218d73ea9fe19bd4e73"},
]
pyexcel = [
{file = "pyexcel-0.6.2-py2.py3-none-any.whl", hash = "sha256:c3f918563073f0ef9a0f9e365ded074bd5baa73ef249b2783dc0d4361ed67d8b"},
{file = "pyexcel-0.6.2.tar.gz", hash = "sha256:408c67a8a2d62a2d6979a3f8d82d6ca80aa127a4b628d046d13cdbe35666b14a"},
]
pyexcel-ezodf = [
{file = "pyexcel-ezodf-0.3.4.tar.gz", hash = "sha256:972eeea9b0e4bab60dfc5cdcb7378cc7ba5e070a0b7282746c0182c5de011ff1"},
{file = "pyexcel_ezodf-0.3.4-py2.py3-none-any.whl", hash = "sha256:a74ac7636a015fff31d35c5350dc5ad347ba98ecb453de4dbcbb9a9168434e8c"},
]
pyexcel-io = [
{file = "pyexcel-io-0.5.20.tar.gz", hash = "sha256:08dfe39553b996359b143de3d9ec43e196f1138d47cabb73af04a16821b84d79"},
{file = "pyexcel_io-0.5.20-py2.py3-none-any.whl", hash = "sha256:2cba956814e72b66072d97b00ede4a084ad881ce72129088eb0dc3c7f3d670cd"},
]
pyexcel-ods = [
{file = "pyexcel-ods-0.5.6.tar.gz", hash = "sha256:3be52fd8aaddbd8bc9286f7eb148f4553d4c97252d695c3a9ec7b95e6666a223"},
{file = "pyexcel_ods-0.5.6-py2.py3-none-any.whl", hash = "sha256:e8d1137da99599e6053c056c21ca601d90f8e000b4db9db679302bbf55d15c0f"},
]
pyexcel-ods3 = [
{file = "pyexcel-ods3-0.5.3.tar.gz", hash = "sha256:3e5f8687a54a1b50d7327145cf4b777a16d9e074370bb24a1193d22cca312e76"},
{file = "pyexcel_ods3-0.5.3-py3-none-any.whl", hash = "sha256:62aa0207ab1074a578d8959bf7d7f7c3710b4bb81423a026d8c03bf0f16b6736"},
]
pyjwt = [
{file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"},
{file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pytest = [
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
]
pytest-env = [
{file = "pytest-env-0.6.2.tar.gz", hash = "sha256:7e94956aef7f2764f3c147d216ce066bf6c42948bb9e293169b1b1c880a580c2"},
]
python-dotenv = [
{file = "python-dotenv-0.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"},
{file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"},
]
pyyaml = [
{file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
{file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
{file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
{file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
{file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
{file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
{file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
{file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
requests = [
{file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"},
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
]
sidb = []
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
starlette = [
{file = "starlette-0.13.4-py3-none-any.whl", hash = "sha256:0fb4b38d22945b46acb880fedee7ee143fd6c0542992501be8c45c0ed737dd1a"},
{file = "starlette-0.13.4.tar.gz", hash = "sha256:04fe51d86fd9a594d9b71356ed322ccde5c9b448fc716ac74155e5821a922f8d"},
]
texttable = [
{file = "texttable-1.6.2-py2.py3-none-any.whl", hash = "sha256:7dc282a5b22564fe0fdc1c771382d5dd9a54742047c61558e071c8cd595add86"},
{file = "texttable-1.6.2.tar.gz", hash = "sha256:eff3703781fbc7750125f50e10f001195174f13825a92a45e9403037d539b4f4"},
]
urllib3 = [
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
]
uvicorn = [
{file = "uvicorn-0.11.6-py3-none-any.whl", hash = "sha256:d19a20b17445708fd222e5a7cfc3eacfb31ac269bc8fefa4920833334e199782"},
{file = "uvicorn-0.11.6.tar.gz", hash = "sha256:467c333c743ec6a3eb545517a0e3cac603becfa40c73c65ed46c3a4bf1c8e2d0"},
]
uvloop = [
{file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"},
{file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"},
{file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"},
{file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"},
{file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"},
{file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"},
{file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"},
{file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"},
{file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
websockets = [
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
{file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"},
{file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"},
{file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"},
{file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"},
{file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"},
{file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"},
{file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"},
{file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"},
{file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"},
{file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"},
{file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"},
{file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"},
{file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"},
{file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"},
{file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"},
{file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"},
{file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"},
{file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"},
{file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"},
{file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
]
zipp = [
{file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
{file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
]

View File

@ -1,35 +0,0 @@
[tool.poetry]
name = "halfapi"
version = "0.1.0"
description = "The core module for an halfORM API"
authors = ["Joël Maizi <joel.maizi@lirmm.fr>", "Maxime Alves <maxime.alves@lirmm.fr>"]
homepage = "https://gite.lirmm.fr/newsi/api/halfapi"
[tool.poetry.dependencies]
click = "^7"
half-orm = { git = "git@gite.lirmm.fr:newsi/halfORM.git" }
PyJWT = "^1"
python = "^3.7"
starlette = "^0"
uvicorn = { version = "^0" }
apidb = { git = "git@gite.lirmm.fr:newsi/db/hop_api.git" }
fastapi = { version = "^0", optional = true }
organigramme = { git = "git@gite.lirmm.fr:newsi/api/organigramme.git", optional = true }
sidb = { git = "git@gite.lirmm.fr:newsi/sidb_halfORM.git", optional = true }
python-dotenv = "^0.14.0"
pyexcel-ods3 = { version = "^0.5.3", optional = true }
[tool.poetry.dev-dependencies]
pytest = "^5"
requests = "^2"
pytest-env = "^0.6.2"
[tool.poetry.extras]
organigramme = [ "fastapi", "organigramme", "sidb", "pyexcel-ods3" ]
[tool.poetry.scripts]
halfapi = 'halfapi.cli:cli'
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

View File

@ -2,4 +2,3 @@ starlette
uvicorn uvicorn
jwt jwt
half_orm @ git+ssh://git@gite.lirmm.fr/newsi/halfORM.git half_orm @ git+ssh://git@gite.lirmm.fr/newsi/halfORM.git
organigramme @ git+ssh://git@gite.lirmm.fr/api/newsi/halfapi.git

62
setup.py Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
from setuptools import setup, find_packages
def get_version(package):
"""
Return package version as listed in `__version__` in `init.py`.
"""
with open(os.path.join(package, "__init__.py")) as f:
return re.search("__version__ = ['\"]([^'\"]+)['\"]", f.read()).group(1)
def get_long_description():
"""
Return the README.
"""
with open("README.md", encoding="utf8") as f:
return f.read()
def get_packages(package):
"""
Return root package and all sub-packages.
"""
return [
dirpath
for dirpath, dirnames, filenames in os.walk(package)
if os.path.exists(os.path.join(dirpath, "__init__.py"))
]
module_name="halfapi"
setup(
name=module_name,
python_requires=">=3.7",
version=get_version(module_name),
url="https://gite.lirmm.fr/newsi/api/halfapi",
long_description=get_long_description(),
long_description_content_type="text/markdown",
packages=get_packages(module_name),
package_data={
'halfapi': ['lib/*', 'models/*']
},
install_requires=[
"click",
"half_orm",
"jwt",
"starlette",
"uvicorn"],
extras_require={
"tests":["pytest", "requests"],
},
entry_points={
"console_scripts":[
"halfapi=halfapi.cli:cli"
]
}
)