Newer
Older
# -*- coding: utf-8 -*-
# copyright 2022 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact https://www.logilab.fr -- mailto:contact@logilab.fr
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Union
from cubicweb import AuthenticationError
from cubicweb.pyramid.core import CubicWebPyramidRequest
from cubicweb.schema_exporters import JSONSchemaExporter
from cubicweb.server.repository import Repository
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.view import view_config
from cubicweb_api.api_transaction import ApiTransactionsRepository
from cubicweb_api.httperrors import get_http_error
from cubicweb_api.jwt_auth import setup_jwt
from marshmallow_dataclass import class_schema
from cubicweb_api.request_params import (
RqlParams,
get_request_params,
LoginParams,
TransactionExecuteParams,
TransactionParams,
)
log = logging.getLogger(__name__)
API_PATTERN_PREFIX = "v1_"
API_ROUTE_PREFIX = "/api/v1/"
DEFAULT_ROUTE_PARAMS = {
"request_method": "POST",
"renderer": "json",
"require_csrf": False,
def get_cw_request(request: Request) -> CubicWebPyramidRequest:
return request.cw_request
def get_cw_repo(req_or_conf: Union[Request, Configurator]) -> Repository:
return req_or_conf.registry["cubicweb.repository"]
def cw_view_config(route_name: str, **kwargs):
return view_config(
route_name=f"{API_PATTERN_PREFIX}{route_name}",
**dict(DEFAULT_ROUTE_PARAMS, **kwargs),
)
@cw_view_config(route_name="schema", request_method="GET")
def schema_route(request: Request):
repo = get_cw_repo(request)
exporter = JSONSchemaExporter()
try:
exported_schema = exporter.export_as_dict(repo.schema)
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
else:
return exported_schema
@cw_view_config(route_name="rql")
def rql_route(request: Request):
schema = class_schema(RqlParams)()
request_params: RqlParams = get_request_params(request, schema)
query = request_params.query
params = request_params.params
raise get_http_error(400, "ValidationError", "Query should not be empty.")
try:
result = get_cw_request(request).execute(query, params)
return result.rows
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name="login")
def login_route(request: Request):
schema = class_schema(LoginParams)()
request_params: LoginParams = get_request_params(request, schema)
login = request_params.login
pwd = request_params.password
repo = get_cw_repo(request)
with repo.internal_cnx() as cnx:
try:
cwuser = repo.authenticate_user(cnx, login, password=pwd)
except AuthenticationError:
raise get_http_error(
401, "AuthenticationFailure", "Login and/or password invalid."
)
else:
request.authentication_policy.remember(
request,
cwuser.eid,
login=cwuser.login,
firstname=cwuser.firstname,
lastname=cwuser.surname,
)
request.response.status_code = 204
@cw_view_config(route_name="transaction/begin")
def transaction_begin_route(request: Request):
transactions = get_cw_repo(request).api_transactions
user = get_cw_request(request).user
return transactions.begin_transaction(user)
@cw_view_config(route_name="transaction/execute")
def transaction_execute_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionExecuteParams)()
params: TransactionExecuteParams = get_request_params(request, schema)
try:
result = transactions[params.uuid].execute(params.query, params.params)
return result.rows
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name="transaction/commit")
def transaction_commit_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionParams)()
params: TransactionParams = get_request_params(request, schema)
uuid = params.uuid
try:
commit_result = transactions[uuid].commit()
transactions[uuid].rollback()
return commit_result
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name="transaction/rollback")
def transaction_rollback_route(request: Request):
transactions = get_cw_repo(request).api_transactions
schema = class_schema(TransactionParams)()
params: TransactionParams = get_request_params(request, schema)
uuid = params.uuid
try:
rollback_result = transactions[uuid].rollback()
transactions.end_transaction(uuid)
return rollback_result
except Exception as e:
log.error(e)
raise get_http_error(400, e.__class__.__name__, str(e))
@cw_view_config(route_name="help", request_method="GET")
def help_route(_request: Request):
"""TO IMPLEMENT"""
return
def add_route(config: Configurator, name: str):
config.add_route(f"{API_PATTERN_PREFIX}{name}", f"/{name}")
def add_cw_routes(config: Configurator):
add_route(config, "schema")
add_route(config, "rql")
add_route(config, "login")
add_route(config, "transaction/begin")
add_route(config, "transaction/execute")
add_route(config, "transaction/commit")
add_route(config, "transaction/rollback")
add_route(config, "help")
config.scan()
def includeme(config: Configurator):
repo = get_cw_repo(config)
repo.api_transactions = ApiTransactionsRepository(repo)
config.include(add_cw_routes, route_prefix=API_ROUTE_PREFIX)