Page MenuHomeSoftware Heritage

api.py
No OneTemporary

#!/usr/bin/env python3
# Copyright (C) 2015 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
from flask import Flask, Response, make_response, request
from swh.loader.git.storage import storage, db, service
from swh.loader.git.protocols import serial
# api's definition
app = Flask(__name__)
def read_request_payload(request):
"""Read the request's payload.
""" # TODO: Check the signed pickled data?
return serial.load(request.stream)
def write_response(data):
"""Write response from data.
"""
return Response(serial.dumps(data), mimetype=serial.MIMETYPE)
@app.route('/')
def hello():
"""A simple api to define what the server is all about.
FIXME: A redirect towards a static page defining the routes would be nice.
"""
return 'Dev SWH API'
# from uri to type
_uri_types = {'revisions': storage.Type.revision,
'directories': storage.Type.directory,
'contents': storage.Type.content,
'releases': storage.Type.release,
'occurrences': storage.Type.occurrence,
'persons': storage.Type.person}
def _do_action_with_payload(conf, action_fn, uri_type, id, map_result_fn):
uri_type_ok = _uri_types.get(uri_type, None)
if uri_type_ok is None:
return make_response('Bad request!', 400)
vcs_object = read_request_payload(request)
vcs_object.update({'id': id,
'type': uri_type_ok})
return action_fn(conf, vcs_object, map_result_fn)
# occurrence type is not dealt the same way
_post_all_uri_types = {'revisions': storage.Type.revision,
'directories': storage.Type.directory,
'contents': storage.Type.content}
@app.route('/vcs/<uri_type>/', methods=['POST'])
def filter_unknowns_type(uri_type):
"""Filters unknown sha1 to the backend and returns them.
"""
if request.headers.get('Content-Type') != serial.MIMETYPE:
return make_response('Bad request. Expected %s data!' % serial.MIMETYPE, 400)
obj_type = _post_all_uri_types.get(uri_type)
if obj_type is None:
return make_response('Bad request. Type not supported!', 400)
sha1s = read_request_payload(request)
config = app.config['conf']
with db.connect(config['db_url']) as db_conn:
unknowns_sha1s = service.filter_unknowns_type(db_conn, obj_type, sha1s)
if unknowns_sha1s is None:
return make_response('Bad request!', 400)
else:
return write_response(unknowns_sha1s)
@app.route('/vcs/persons/', methods=['POST'])
def post_person():
"""Find a person.
"""
if request.headers.get('Content-Type') != serial.MIMETYPE:
return make_response('Bad request. Expected %s data!' % serial.MIMETYPE, 400)
origin = read_request_payload(request)
config = app.config['conf']
with db.connect(config['db_url']) as db_conn:
try:
person_found = service.find_person(db_conn, origin)
if person_found:
return write_response(person_found)
else:
return make_response('Person not found!', 404)
except:
return make_response('Bad request!', 400)
@app.route('/origins/', methods=['POST'])
def post_origin():
"""Find an origin.
"""
if request.headers.get('Content-Type') != serial.MIMETYPE:
return make_response('Bad request. Expected %s data!' % serial.MIMETYPE, 400)
origin = read_request_payload(request)
config = app.config['conf']
with db.connect(config['db_url']) as db_conn:
try:
origin_found = service.find_origin(db_conn, origin)
if origin_found:
return write_response(origin_found)
else:
return make_response('Origin not found!', 404)
except:
return make_response('Bad request!', 400)
@app.route('/origins/', methods=['PUT'])
def put_origin():
"""Create an origin or returns it if already existing.
"""
if request.headers.get('Content-Type') != serial.MIMETYPE:
return make_response('Bad request. Expected %s data!' % serial.MIMETYPE, 400)
origin = read_request_payload(request)
config = app.config['conf']
with db.connect(config['db_url']) as db_conn:
try:
origin_found = service.add_origin(db_conn, origin)
return write_response(origin_found) # FIXME: 204
except:
return make_response('Bad request!', 400)
@app.route('/vcs/<uri_type>/', methods=['PUT'])
def put_all(uri_type):
"""Store or update given objects (uri_type in {contents, directories, releases).
"""
if request.headers.get('Content-Type') != serial.MIMETYPE:
return make_response('Bad request. Expected %s data!' % serial.MIMETYPE, 400)
payload = read_request_payload(request)
obj_type = _uri_types[uri_type]
config = app.config['conf']
with db.connect(config['db_url']) as db_conn:
service.persist(db_conn, config, obj_type, payload)
return make_response('Successful creation!', 204)
def add_object(config, vcs_object, map_result_fn):
"""Add object in storage.
- config is the configuration needed for the backend to execute query
- vcs_object is the object to look for in the backend
- map_result_fn is a mapping function which takes the backend's result
and transform its output accordingly.
This function returns an http response of the result.
"""
type = vcs_object['type']
id = vcs_object['id']
logging.debug('storage %s %s' % (type, id))
with db.connect(config['db_url']) as db_conn:
res = service.add_objects(db_conn, config, type, [vcs_object])
return make_response(map_result_fn(id, res), 204)
def _do_lookup(conf, uri_type, id, map_result_fn):
"""Looking up type object with sha1.
- config is the configuration needed for the backend to execute query
- vcs_object is the object to look for in the backend
- map_result_fn is a mapping function which takes the backend's result
and transform its output accordingly.
This function returns an http response of the result.
"""
uri_type_ok = _uri_types.get(uri_type, None)
if not uri_type_ok:
return make_response('Bad request!', 400)
with db.connect(conf['db_url']) as db_conn:
res = storage.find(db_conn, id, uri_type_ok)
if res:
return write_response(map_result_fn(id, res)) # 200
return make_response('Not found!', 404)
@app.route('/vcs/occurrences/<id>')
def list_occurrences_for(id):
"""Return the occurrences pointing to the revision id.
"""
return _do_lookup(app.config['conf'],
'occurrences',
id,
lambda _, result: list(map(lambda col: col[1], result)))
@app.route('/vcs/<uri_type>/<id>')
def object_exists_p(uri_type, id):
"""Assert if the object with sha1 id, of type uri_type, exists.
"""
return _do_lookup(app.config['conf'],
uri_type,
id,
lambda sha1, _: {'id': sha1})
@app.route('/vcs/<uri_type>/<id>', methods=['PUT'])
def put_object(uri_type, id):
"""Put an object in storage.
"""
return _do_action_with_payload(app.config['conf'],
add_object,
uri_type,
id,
lambda sha1, _2: sha1) # FIXME: use id or result instead
def run(conf):
"""Run the api's server.
conf is a dictionary of keywords:
- 'db_url' the db url's access (through psycopg2 format)
- 'content_storage_dir' revisions/directories/contents storage on disk
- 'host' to override the default 127.0.0.1 to open or not the server to
the world
- 'port' to override the default of 5000 (from the underlying layer:
flask)
- 'debug' activate the verbose logs
"""
print("""SWH Api run
host: %s
port: %s
debug: %s""" % (conf['host'], conf.get('port', None), conf['debug']))
# app.config is the app's state (accessible)
app.config.update({'conf': conf})
app.run(host=conf['host'],
port=conf.get('port', None),
debug=conf['debug'] == 'true')

File Metadata

Mime Type
text/x-python
Expires
Jul 4 2025, 7:32 PM (7 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3356057

Event Timeline