eventcally/project/api/__init__.py
2023-03-15 20:55:17 +01:00

206 lines
6.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from apispec import APISpec
from apispec.exceptions import DuplicateComponentNameError
from apispec.ext.marshmallow import MarshmallowPlugin
from flask import url_for
from flask_apispec.extension import FlaskApiSpec
from flask_babelex import gettext
from flask_marshmallow import Marshmallow
from flask_restful import Api
from marshmallow import ValidationError
from psycopg2.errorcodes import CHECK_VIOLATION, UNIQUE_VIOLATION
from sqlalchemy.exc import IntegrityError
from werkzeug.exceptions import HTTPException, UnprocessableEntity
from project import app
from project.utils import get_localized_scope
class RestApi(Api):
def handle_error(self, err):
from project.api.schemas import (
ErrorResponseSchema,
UnprocessableEntityResponseSchema,
)
schema = None
data = {}
code = 500
if isinstance(err, IntegrityError) and err.orig:
if err.orig.pgcode == UNIQUE_VIOLATION:
data["name"] = "Unique Violation"
data[
"message"
] = "An entry with the entered values already exists. Duplicate entries are not allowed."
elif err.orig.pgcode == CHECK_VIOLATION:
data["name"] = "Check Violation"
if hasattr(err.orig, "message") and getattr(err.orig, "message", None):
data["message"] = err.orig.message
else:
data["message"] = "Action violates database constraint."
else:
data["name"] = "Integrity Error"
data["message"] = "Action violates database integrity."
code = 400
schema = ErrorResponseSchema()
elif isinstance(err, HTTPException):
data["name"] = err.name
data["message"] = err.description
code = err.code
if isinstance(err, UnprocessableEntity):
data["name"] = err.name
data["message"] = err.description
code = err.code
schema = UnprocessableEntityResponseSchema()
if (
hasattr(err, "exc")
and err.exc
and isinstance(err.exc, ValidationError)
):
self.fill_validation_data(err.exc, data)
else:
schema = ErrorResponseSchema()
elif isinstance(err, ValidationError):
data["name"] = "Unprocessable Entity"
data[
"message"
] = "The request was well-formed but was unable to be followed due to semantic errors."
code = 422
schema = UnprocessableEntityResponseSchema()
self.fill_validation_data(err, data)
# Call default error handler that propagates error further
if code >= 500:
try:
super().handle_error(err)
except Exception:
if not schema:
raise
if data and "message" in data:
data["message"] = gettext(data["message"])
return schema.dump(data), code
def fill_validation_data(self, err: ValidationError, data: dict):
if (
getattr(err, "args", None)
and isinstance(err.args, tuple)
and len(err.args) > 0
):
arg = err.args[0]
if isinstance(arg, dict):
errors = []
for field, item in arg.items():
messages = list()
if isinstance(item, list):
messages = item
elif isinstance(item, dict):
for item_value in item.values():
if isinstance(item_value, list) or isinstance(
item_value, tuple
):
messages.extend(item_value)
for message in messages:
error = {"field": field, "message": message}
errors.append(error)
if len(errors) > 0:
data["errors"] = errors
scope_list = [
"openid",
"profile",
"user:read",
"user:write",
"organizer:write",
"place:write",
"event:write",
"organization:read",
"organization:write",
"eventlist:write",
"customwidget:write",
]
scopes = {k: get_localized_scope(k) for v, k in enumerate(scope_list)}
rest_api = RestApi(app, "/api/v1", catch_all_404s=True)
marshmallow = Marshmallow(app)
marshmallow_plugin = MarshmallowPlugin()
app.config.update(
{
"APISPEC_SPEC": APISpec(
title="Event calendar API",
version="0.1.0",
plugins=[marshmallow_plugin],
openapi_version="2.0",
info=dict(
description="This API provides endpoints to interact with the event calendar data."
),
),
}
)
api_docs = FlaskApiSpec(app)
def enum_to_properties(self, field, **kwargs):
"""
Add an OpenAPI extension for marshmallow_enum.EnumField instances
"""
import marshmallow_enum
if isinstance(field, marshmallow_enum.EnumField):
return {"type": "string", "enum": [m.name for m in field.enum]}
return {}
def add_api_resource(resource, url, endpoint):
rest_api.add_resource(resource, url, endpoint=endpoint)
api_docs.register(resource, endpoint=endpoint)
def add_oauth2_scheme_with_transport(insecure: bool):
if insecure:
authorizationUrl = url_for("authorize", _external=True)
tokenUrl = url_for("issue_token", _external=True)
else:
authorizationUrl = url_for("authorize", _external=True, _scheme="https")
tokenUrl = url_for("issue_token", _external=True, _scheme="https")
oauth2_scheme = {
"type": "oauth2",
"authorizationUrl": authorizationUrl,
"tokenUrl": tokenUrl,
"flow": "accessCode",
"scopes": {k: k for _, k in enumerate(scope_list)},
}
try:
api_docs.spec.components.security_scheme("oauth2", oauth2_scheme)
except DuplicateComponentNameError: # pragma: no cover
pass
marshmallow_plugin.converter.add_attribute_function(enum_to_properties)
import project.api.custom_widget.resources
import project.api.dump.resources
import project.api.event.resources
import project.api.event_category.resources
import project.api.event_date.resources
import project.api.event_list.resources
import project.api.event_reference.resources
import project.api.organization.resources
import project.api.organization_invitation.resources
import project.api.organization_relation.resources
import project.api.organizer.resources
import project.api.place.resources
import project.api.user.resources