mirror of
https://github.com/lucaspalomodevelop/eventcally.git
synced 2026-03-13 00:07:22 +00:00
Setup Rest API with Swagger #50
This commit is contained in:
parent
57561e9b03
commit
8fb3b83530
@ -10,6 +10,11 @@ from flask_cors import CORS
|
||||
from flask_qrcode import QRcode
|
||||
from flask_mail import Mail, email_dispatched
|
||||
from flask_migrate import Migrate
|
||||
from flask_marshmallow import Marshmallow
|
||||
from flask_restful import Api
|
||||
from apispec import APISpec
|
||||
from apispec.ext.marshmallow import MarshmallowPlugin
|
||||
from flask_apispec.extension import FlaskApiSpec
|
||||
|
||||
# Create app
|
||||
app = Flask(__name__)
|
||||
@ -42,6 +47,21 @@ babel = Babel(app)
|
||||
# cors
|
||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
# API
|
||||
rest_api = Api(app, "/api/v1")
|
||||
marshmallow = Marshmallow(app)
|
||||
app.config.update(
|
||||
{
|
||||
"APISPEC_SPEC": APISpec(
|
||||
title="Oveda API",
|
||||
version="1.0.0",
|
||||
plugins=[MarshmallowPlugin()],
|
||||
openapi_version="2.0",
|
||||
),
|
||||
}
|
||||
)
|
||||
api_docs = FlaskApiSpec(app)
|
||||
|
||||
# Mail
|
||||
mail_server = os.getenv("MAIL_SERVER")
|
||||
|
||||
@ -114,6 +134,9 @@ from project.views import (
|
||||
widget,
|
||||
)
|
||||
|
||||
# API Resources
|
||||
import project.api
|
||||
|
||||
# Command line
|
||||
import project.cli.event
|
||||
import project.cli.user
|
||||
|
||||
3
project/api/__init__.py
Normal file
3
project/api/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
import project.api.event.resources
|
||||
import project.api.organization.resources
|
||||
import project.api.organizer.resources
|
||||
0
project/api/event/__init__.py
Normal file
0
project/api/event/__init__.py
Normal file
32
project/api/event/resources.py
Normal file
32
project/api/event/resources.py
Normal file
@ -0,0 +1,32 @@
|
||||
from project import rest_api, api_docs
|
||||
from flask_apispec import marshal_with, doc, use_kwargs
|
||||
from flask_apispec.views import MethodResource
|
||||
from project.api.event.schemas import (
|
||||
EventSchema,
|
||||
EventListRequestSchema,
|
||||
EventListResponseSchema,
|
||||
)
|
||||
from project.models import Event
|
||||
|
||||
|
||||
class EventListResource(MethodResource):
|
||||
@doc(tags=["Events"])
|
||||
@use_kwargs(EventListRequestSchema, location=("query"))
|
||||
@marshal_with(EventListResponseSchema)
|
||||
def get(self, **kwargs):
|
||||
pagination = Event.query.paginate()
|
||||
return pagination
|
||||
|
||||
|
||||
class EventResource(MethodResource):
|
||||
@doc(tags=["Events"])
|
||||
@marshal_with(EventSchema)
|
||||
def get(self, id):
|
||||
return Event.query.get_or_404(id)
|
||||
|
||||
|
||||
rest_api.add_resource(EventListResource, "/events")
|
||||
api_docs.register(EventListResource)
|
||||
|
||||
rest_api.add_resource(EventResource, "/events/<int:id>")
|
||||
api_docs.register(EventResource)
|
||||
79
project/api/event/schemas.py
Normal file
79
project/api/event/schemas.py
Normal file
@ -0,0 +1,79 @@
|
||||
from project import marshmallow
|
||||
from marshmallow import fields
|
||||
from project.models import Event
|
||||
|
||||
|
||||
class EventSchema(marshmallow.SQLAlchemySchema):
|
||||
class Meta:
|
||||
model = Event
|
||||
|
||||
id = marshmallow.auto_field()
|
||||
name = marshmallow.auto_field()
|
||||
description = marshmallow.auto_field()
|
||||
external_link = marshmallow.auto_field()
|
||||
ticket_link = marshmallow.auto_field()
|
||||
tags = marshmallow.auto_field()
|
||||
kid_friendly = marshmallow.auto_field()
|
||||
accessible_for_free = marshmallow.auto_field()
|
||||
age_from = marshmallow.auto_field()
|
||||
age_to = marshmallow.auto_field()
|
||||
|
||||
organization = marshmallow.HyperlinkRelated(
|
||||
"organizationresource", attribute="admin_unit"
|
||||
)
|
||||
organizer = marshmallow.URLFor(
|
||||
"organizerresource",
|
||||
values=dict(organization_id="<admin_unit_id>", organizer_id="<organizer_id>"),
|
||||
)
|
||||
|
||||
|
||||
class EventListItemSchema(marshmallow.SQLAlchemySchema):
|
||||
class Meta:
|
||||
model = Event
|
||||
|
||||
id = marshmallow.auto_field()
|
||||
href = marshmallow.URLFor("eventresource", values=dict(id="<id>"))
|
||||
name = marshmallow.auto_field()
|
||||
start = marshmallow.auto_field()
|
||||
end = marshmallow.auto_field()
|
||||
recurrence_rule = marshmallow.auto_field()
|
||||
|
||||
|
||||
class EventListRequestSchema(marshmallow.Schema):
|
||||
page = fields.Integer(
|
||||
required=False,
|
||||
default=1,
|
||||
metadata={"description": "The page number (1 indexed)."},
|
||||
)
|
||||
per_page = fields.Integer(
|
||||
required=False, default=20, metadata={"description": "Items per page"}
|
||||
)
|
||||
|
||||
|
||||
class EventListResponseSchema(marshmallow.Schema):
|
||||
has_next = fields.Boolean(
|
||||
required=True, metadata={"description": "True if a next page exists."}
|
||||
)
|
||||
has_prev = fields.Boolean(
|
||||
required=True, metadata={"description": "True if a previous page exists."}
|
||||
)
|
||||
next_num = fields.Integer(
|
||||
required=False, metadata={"description": "Number of the next page."}
|
||||
)
|
||||
prev_num = fields.Integer(
|
||||
required=False, metadata={"description": "Number of the previous page."}
|
||||
)
|
||||
page = fields.Integer(
|
||||
required=True, metadata={"description": "The current page number (1 indexed)."}
|
||||
)
|
||||
pages = fields.Integer(
|
||||
required=True, metadata={"description": "The total number of pages."}
|
||||
)
|
||||
per_page = fields.Integer(required=True, metadata={"description": "Items per page"})
|
||||
total = fields.Integer(
|
||||
required=True,
|
||||
metadata={"description": "The total number of items matching the query"},
|
||||
)
|
||||
items = fields.List(
|
||||
fields.Nested(EventListItemSchema), metadata={"description": "Events"}
|
||||
)
|
||||
0
project/api/organization/__init__.py
Normal file
0
project/api/organization/__init__.py
Normal file
16
project/api/organization/resources.py
Normal file
16
project/api/organization/resources.py
Normal file
@ -0,0 +1,16 @@
|
||||
from project import rest_api, api_docs
|
||||
from flask_apispec import marshal_with, doc
|
||||
from flask_apispec.views import MethodResource
|
||||
from project.api.organization.schemas import OrganizationSchema
|
||||
from project.models import AdminUnit
|
||||
|
||||
|
||||
class OrganizationResource(MethodResource):
|
||||
@doc(tags=["Organizations"])
|
||||
@marshal_with(OrganizationSchema)
|
||||
def get(self, id):
|
||||
return AdminUnit.query.get_or_404(id)
|
||||
|
||||
|
||||
rest_api.add_resource(OrganizationResource, "/organizations/<int:id>")
|
||||
api_docs.register(OrganizationResource)
|
||||
14
project/api/organization/schemas.py
Normal file
14
project/api/organization/schemas.py
Normal file
@ -0,0 +1,14 @@
|
||||
from project import marshmallow
|
||||
from project.models import AdminUnit
|
||||
|
||||
|
||||
class OrganizationSchema(marshmallow.SQLAlchemySchema):
|
||||
class Meta:
|
||||
model = AdminUnit
|
||||
|
||||
id = marshmallow.auto_field()
|
||||
name = marshmallow.auto_field()
|
||||
url = marshmallow.auto_field()
|
||||
email = marshmallow.auto_field()
|
||||
phone = marshmallow.auto_field()
|
||||
fax = marshmallow.auto_field()
|
||||
0
project/api/organizer/__init__.py
Normal file
0
project/api/organizer/__init__.py
Normal file
19
project/api/organizer/resources.py
Normal file
19
project/api/organizer/resources.py
Normal file
@ -0,0 +1,19 @@
|
||||
from project import rest_api, api_docs
|
||||
from flask_apispec import marshal_with, doc
|
||||
from flask_apispec.views import MethodResource
|
||||
from project.api.organizer.schemas import OrganizerSchema
|
||||
from project.models import EventOrganizer
|
||||
|
||||
|
||||
class OrganizerResource(MethodResource):
|
||||
@doc(tags=["Organizers"])
|
||||
@marshal_with(OrganizerSchema)
|
||||
def get(self, organization_id, organizer_id):
|
||||
return EventOrganizer.query.get_or_404(organizer_id)
|
||||
|
||||
|
||||
rest_api.add_resource(
|
||||
OrganizerResource,
|
||||
"/organizations/<int:organization_id>/organizers/<int:organizer_id>",
|
||||
)
|
||||
api_docs.register(OrganizerResource)
|
||||
10
project/api/organizer/schemas.py
Normal file
10
project/api/organizer/schemas.py
Normal file
@ -0,0 +1,10 @@
|
||||
from project import marshmallow
|
||||
from project.models import EventOrganizer
|
||||
|
||||
|
||||
class OrganizerSchema(marshmallow.SQLAlchemySchema):
|
||||
class Meta:
|
||||
model = EventOrganizer
|
||||
|
||||
id = marshmallow.auto_field()
|
||||
name = marshmallow.auto_field()
|
||||
@ -29,7 +29,7 @@ from sqlalchemy import and_
|
||||
|
||||
|
||||
def _current_user_id_or_none():
|
||||
if current_user.is_authenticated:
|
||||
if current_user and current_user.is_authenticated:
|
||||
return current_user.id
|
||||
|
||||
return None
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
alembic==1.4.3
|
||||
aniso8601==8.1.0
|
||||
apispec==4.0.0
|
||||
apispec-webframeworks==0.5.2
|
||||
appdirs==1.4.4
|
||||
attrs==20.3.0
|
||||
Babel==2.9.0
|
||||
@ -22,15 +25,18 @@ email-validator==1.1.2
|
||||
filelock==3.0.12
|
||||
flake8==3.8.4
|
||||
Flask==1.1.2
|
||||
flask-apispec==0.11.0
|
||||
Flask-BabelEx==0.9.4
|
||||
Flask-Bootstrap==3.3.7.1
|
||||
Flask-Cors==3.0.9
|
||||
Flask-Dance==3.2.0
|
||||
Flask-Login==0.5.0
|
||||
Flask-Mail==0.9.1
|
||||
flask-marshmallow==0.14.0
|
||||
Flask-Migrate==2.5.3
|
||||
Flask-Principal==0.4.0
|
||||
Flask-QRcode==3.0.0
|
||||
Flask-RESTful==0.3.8
|
||||
Flask-Security-Too==3.4.4
|
||||
Flask-SQLAlchemy==2.4.4
|
||||
Flask-WTF==0.14.3
|
||||
@ -42,9 +48,13 @@ importlib-metadata==3.1.1
|
||||
iniconfig==1.1.1
|
||||
itsdangerous==1.1.0
|
||||
Jinja2==2.11.2
|
||||
jsonschema==3.2.0
|
||||
Mako==1.1.3
|
||||
MarkupSafe==1.1.1
|
||||
marshmallow==3.10.0
|
||||
marshmallow-sqlalchemy==0.24.1
|
||||
mccabe==0.6.1
|
||||
mistune==0.8.4
|
||||
mypy-extensions==0.4.3
|
||||
nodeenv==1.5.0
|
||||
oauthlib==3.1.0
|
||||
@ -60,6 +70,7 @@ pycodestyle==2.6.0
|
||||
pycparser==2.20
|
||||
pyflakes==2.2.0
|
||||
pyparsing==2.4.7
|
||||
pyrsistent==0.17.3
|
||||
pytest==6.1.2
|
||||
pytest-cov==2.10.1
|
||||
pytest-mock==3.3.1
|
||||
@ -78,6 +89,7 @@ soupsieve==2.1
|
||||
speaklater==1.3
|
||||
SQLAlchemy==1.3.20
|
||||
SQLAlchemy-Utils==0.36.8
|
||||
swagger-spec-validator==2.7.3
|
||||
toml==0.10.2
|
||||
typed-ast==1.4.1
|
||||
typing-extensions==3.7.4.3
|
||||
@ -85,6 +97,7 @@ urllib3==1.26.2
|
||||
URLObject==2.4.3
|
||||
virtualenv==20.2.2
|
||||
visitor==0.1.3
|
||||
webargs==7.0.1
|
||||
Werkzeug==1.0.1
|
||||
WTForms==2.3.3
|
||||
WTForms-SQLAlchemy==0.2
|
||||
|
||||
0
tests/api/__init__.py
Normal file
0
tests/api/__init__.py
Normal file
14
tests/api/test_event.py
Normal file
14
tests/api/test_event.py
Normal file
@ -0,0 +1,14 @@
|
||||
def test_read(client, seeder, utils):
|
||||
user_id, admin_unit_id = seeder.setup_base()
|
||||
event_id = seeder.create_event(admin_unit_id)
|
||||
|
||||
url = utils.get_url("eventresource", id=event_id)
|
||||
utils.get_ok(url)
|
||||
|
||||
|
||||
def test_list(client, seeder, utils):
|
||||
user_id, admin_unit_id = seeder.setup_base()
|
||||
seeder.create_event(admin_unit_id)
|
||||
|
||||
url = utils.get_url("eventlistresource")
|
||||
utils.get_ok(url)
|
||||
5
tests/api/test_organization.py
Normal file
5
tests/api/test_organization.py
Normal file
@ -0,0 +1,5 @@
|
||||
def test_read(client, seeder, utils):
|
||||
user_id, admin_unit_id = seeder.setup_base()
|
||||
|
||||
url = utils.get_url("organizationresource", id=admin_unit_id)
|
||||
utils.get_ok(url)
|
||||
8
tests/api/test_organizer.py
Normal file
8
tests/api/test_organizer.py
Normal file
@ -0,0 +1,8 @@
|
||||
def test_read(client, seeder, utils):
|
||||
user_id, admin_unit_id = seeder.setup_base()
|
||||
organizer_id = seeder.upsert_default_event_organizer(admin_unit_id)
|
||||
|
||||
url = utils.get_url(
|
||||
"organizerresource", organization_id=admin_unit_id, organizer_id=organizer_id
|
||||
)
|
||||
utils.get_ok(url)
|
||||
10
tests/api/test_swagger.py
Normal file
10
tests/api/test_swagger.py
Normal file
@ -0,0 +1,10 @@
|
||||
from swagger_spec_validator import validator20
|
||||
|
||||
|
||||
def test_swagger(utils):
|
||||
response = utils.get_ok("/swagger/")
|
||||
validator20.validate_spec(response.json)
|
||||
|
||||
|
||||
def test_swagger_ui(utils):
|
||||
utils.get_ok("/swagger-ui/")
|
||||
Loading…
x
Reference in New Issue
Block a user