Skip to content

Commit

Permalink
Merge pull request #27 from kimbugp/develop
Browse files Browse the repository at this point in the history
Implement query parser (#26)
  • Loading branch information
kimbugp committed Aug 13, 2019
2 parents 04f16ac + 757fc78 commit 57c242d
Show file tree
Hide file tree
Showing 26 changed files with 224 additions and 119 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
.pytest_cache/v/cache/lastfailed
.coverage
htmlcov
prod.env
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ To setup for development with Docker after cloning the repository please do/run

- `cd <project dir>` to check into the dir
- create a `.env` file from the template `.env.example` and update the variables
- create a `prod.env` file from the template `prod.env.example` and update the variables
- `docker-compose build` to build the application images
- `docker-compose up -d` to start the api after the previous command is successful

Expand Down
9 changes: 7 additions & 2 deletions apps/cinema/schema/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
from datetime import datetime
from .schema_utils import validate_date

from .schema_utils import validate_date

from webargs import fields as flds


def param(func):
return flds.Nested({"operator": flds.Str(required=True), "field": flds.Str(required=True), "value": func})
50 changes: 50 additions & 0 deletions apps/cinema/schema/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import re

from webargs import core
from webargs.flaskparser import FlaskParser
from functools import wraps

suffix = {'ne': '!=', 'gt': '>', 'gte': '>=',
'lt': '<', 'lte': '<=', 'eq': '='}


param_regex = re.compile(r"(^[a-zA-Z]+[_]?[a-zA-Z]+)|(gt|lt|lte|eq|ne)\b")


class CustomParamsParser(FlaskParser):
def parse_querystring(self, req, name, field):
return core.get_value(_structure_dict(req.args), name, field)


def _structure_dict(dict_):
def structure_dict_pair(r, key, value):
m = param_regex.findall(key)
operator = 'eq'
if m:
((col, *_), *t) = m
if t:
(*_, (*_, operator)) = t
if r.get(col) is None:
r[col] = {'operator': suffix.get(
operator), 'value': value, 'field': col}
r = {}
for key, value in dict_.items():
structure_dict_pair(r, key, value)
return r


parser = CustomParamsParser()
use_kwargs = parser.use_kwargs


def use_args(fields):

def decorated(func):
@wraps(func)
@parser.use_args(fields)
def inner(*args, **kwargs):
*args, params = args
params = [item for item in params.values()]
return func(*args, params, **kwargs)
return inner
return decorated
5 changes: 4 additions & 1 deletion apps/cinema/schema/schema_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import jsonschema

from datetime import datetime

import jsonschema

from apps.middlewares.validation import ValidationError


Expand Down
29 changes: 14 additions & 15 deletions apps/cinema/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@
from werkzeug.security import check_password_hash, generate_password_hash

from apps.cinema import api
from apps.cinema.schema import param, validate_date
from apps.cinema.schema.parser import use_args
from apps.cinema.schema.user_schema import *
from apps.middlewares.auth import generate_token, is_admin, token_header
from apps.middlewares.validation import ValidationError
from controllers.user_controller import UserController
from flask_restplus import Resource
from flask_restplus import Resource, abort
from webargs import fields as flds
from webargs.flaskparser import use_args
from apps.cinema.schema import validate_date


user_args = {'id': flds.Int(),
'email': flds.Str(),
'report': flds.Bool(),
'name': flds.Str(),
'ticket_startdate': flds.Str(validate=validate_date),
'ticket_enddate': flds.Str(validate=validate_date),
'total': flds.Float()}
user_args = {'id': param(flds.Int(required=True)),
'email': param(flds.Str()),
'report': param(flds.Bool()),
'name': param(flds.Str()),
'ticket_startdate': param(flds.Str(validate=validate_date)),
'ticket_enddate': param(flds.Str(validate=validate_date)),
'total': param(flds.Float())}


@api.route('/auth', endpoint='user')
Expand All @@ -31,7 +30,7 @@ def post(self):
user['password'] = generate_password_hash(
user['password'], method='sha256')
controller = UserController()
if not controller.find(email=user.get('email')):
if not controller.find_one(email=user.get('email')):
user = controller.insert(user)
return user, 201
raise ValidationError(message='error', status_code=400, payload={
Expand All @@ -40,7 +39,7 @@ def post(self):
@api.marshal_with(user_schema_fields, envelope='user', skip_none=True)
@api.doc(security='Authorisation')
@token_header
def get(self):
def get(self, **kwargs):
return request.user._asdict(), 200


Expand Down Expand Up @@ -68,6 +67,6 @@ class UserQueryAndReport(Resource):
@token_header
@is_admin
@use_args(user_args)
def get(self, args):
def get(self, params, **kwargs):
controller = UserController()
return controller.find(serialize=True, **args), 200
return controller.find(serialize=True, params=params, **kwargs), 200
10 changes: 5 additions & 5 deletions apps/cinema/views/cinemahall.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from flask import current_app, request

from apps.cinema import api
from apps.cinema.schema import param, validate_date
from apps.cinema.schema.cinema_schema import *
from apps.cinema.schema.parser import use_args
from apps.middlewares.auth import is_admin, token_header
from controllers.cinema import CinemaController
from controllers.seats import SeatController
from flask_restplus import Resource
from models import CinemaHall
from utils import dict_to_tuple, find_or_404
from webargs import fields as flds
from webargs.flaskparser import use_args

cinema_args = {'id': flds.Int()}
cinema_args = {'id': param(flds.Int(required=True))}


@api.route('/cinema', endpoint='cinemas')
Expand All @@ -26,16 +27,15 @@ def post(self):
controller = CinemaController()
cinema = controller.insert(body)[0]
seats_dict = process_seats(seats, cinema.get('id'))
SeatController().insert(seats_dict)
return {'seats': SeatController().insert(seats_dict), **cinema}, 201

@api.marshal_with(cinema_response_schema, envelope='cinema', skip_none=True)
@token_header
@is_admin
@use_args(cinema_args)
def get(self, args):
def get(self, params, **kwargs):
controller = CinemaController()
return controller.find(serialize=True, **args), 200
return controller.find(serialize=True, params=params), 200


@api.route('/cinema/<int:cinema_id>', endpoint='cinema')
Expand Down
7 changes: 6 additions & 1 deletion apps/cinema/views/errors.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@

from flask import jsonify, make_response

from apps.cinema import api
from apps.middlewares.validation import ValidationError
from apps.cinema.schema.parser import parser


@api.errorhandler(ValidationError)
def handle_invalid_usage(error):
return error.to_dict(), error.status_code


@parser.error_handler
def handle_request_parsing_error(err, req, schema, error_status_code, error_headers):
raise ValidationError('error', payload={**err.messages})
6 changes: 3 additions & 3 deletions apps/cinema/views/movies.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def post(self):
@api.marshal_with(movie_response_schema, envelope='movies', skip_none=True)
@token_header
@is_admin
def get(self):
def get(self, **kwargs):
controller = MovieController()
return controller.find(serialize=True), 200

Expand All @@ -33,9 +33,9 @@ def get(self):
class SingleMovieResource(Resource):
@api.marshal_with(movie_response_schema, envelope='movie', skip_none=True)
@token_header
def get(self, movie_id):
def get(self, movie_id, **kwargs):
controller = MovieController()
return controller.find(id=movie_id, serialize=True), 200
return controller.find_one(id=movie_id, serialize=True), 200

@api.marshal_with(movie_response_schema, envelope='movie', skip_none=True)
@token_header
Expand Down
29 changes: 17 additions & 12 deletions apps/cinema/views/showtimes.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
from datetime import datetime

from flask import request
from flask_restplus import Resource
from webargs import fields as flds # noqa

from apps.cinema import api
from apps.cinema.schema import validate_date
from apps.cinema.schema.parser import use_args
from apps.cinema.schema.showtime_schema import *
from apps.middlewares.auth import is_admin, token_header
from controllers.show_time_controller import ShowTimeController
from flask_restplus import Resource
from webargs import fields as flds # noqa
from webargs.flaskparser import use_args
from apps.cinema.schema import param

showtime_args = {"start_date": flds.Str(validate=validate_date), 'id': flds.Int()}
showtime_args = {
"start_date": param(flds.Str(required=True,validate=validate_date)),
'id': param(flds.Int(required=True))
}


@api.route('/showtime', endpoint='showtimes')
class ShowTimeEndpoint(Resource):
@api.marshal_with(showtimes_schema, envelope='showtimes',skip_none=True)
@api.marshal_with(showtimes_schema, envelope='showtimes', skip_none=True)
@token_header
@use_args(showtime_args)
def get(self, args):
def get(self, params):
showtimes = ShowTimeController()
return showtimes.find(**args), 200
params = {items.get('field'): items.get('value') for items in params}
return showtimes.find(**params), 200

@api.marshal_with(showtimes_schema, envelope='showtimes',skip_none=True)
@api.marshal_with(showtimes_schema, envelope='showtimes', skip_none=True)
@token_header
@is_admin
def post(self):
Expand All @@ -35,13 +40,13 @@ def post(self):

@api.route('/showtime/<int:showtime_id>', endpoint='showtime')
class ShowTimeEndpoint(Resource):
@api.marshal_with(showtimes_schema, envelope='showtime',skip_none=True)
@api.marshal_with(showtimes_schema, envelope='showtime', skip_none=True)
@token_header
def get(self, showtime_id):
def get(self, showtime_id, **kwargs):
showtimes = ShowTimeController()
return showtimes.find(id=showtime_id, start_date=datetime(2019, 1, 1)), 200
return showtimes.find(id=showtime_id, start_date=datetime(2018, 1, 1)), 200

@api.marshal_with(showtimes_schema, envelope='showtimes',skip_none=True)
@api.marshal_with(showtimes_schema, envelope='showtimes', skip_none=True)
@token_header
@is_admin
def put(self, showtime_id):
Expand Down
32 changes: 16 additions & 16 deletions apps/cinema/views/tickets.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from flask import Blueprint, jsonify, request

from apps.cinema import api
from apps.cinema.schema.ticket_schema import *
from apps.cinema.schema import validate_date
from apps.cinema.schema.parser import use_args
from apps.cinema.schema.ticket_schema import *
from apps.middlewares.auth import token_header
from controllers.ticket_controller import TicketController
from flask_restplus import Resource
from webargs import fields
from webargs.flaskparser import use_args
from webargs import fields as flds
from apps.cinema.schema import param

ticket_args = {'user_id': fields.Int(),
'date_created': fields.Str(validate=validate_date),
'show_date_time': fields.Str(validate=validate_date),
'movie_id': fields.Int(),
'price': fields.Float(),
'id': fields.Int()}
ticket_args = {'user_id': param(flds.Int(required=True)),
'date_created': param(flds.Str(validate=validate_date)),
'show_date_time': param(flds.Str(validate=validate_date)),
'movie_id': param(flds.Int()),
'price': param(flds.Float()),
'id': param(flds.Int())}


@api.route('/ticket', endpoint='tickets')
Expand All @@ -40,13 +41,13 @@ def post(self):
@api.expect(ticket_schema)
@token_header
@use_args(ticket_args)
def get(self, args):
def get(self, params, **kwargs):
user_id = request.user.id
controller = TicketController()
if not request.user.is_staff:
args['user_id'] = request.user.id
tickets = controller.find(
serialize=True, operator='AND', check='=', ** args)
params.append({'operator':'=','value':request.user.id,'field':'user_id'})

tickets = controller.find(serialize=True, operator='AND', params=params, **kwargs)
return tickets, 200


Expand All @@ -55,9 +56,8 @@ class TicketBooking(Resource):
@api.marshal_with(ticket_response_body, envelope='ticket', skip_none=True)
@api.expect(ticket_schema)
@token_header
def get(self, ticket_id):
def get(self, ticket_id, **kwargs):
user_id = request.user.id
controller = TicketController()
ticket = controller.find_one(
operator='AND', user_id=user_id, id=ticket_id, serialize=True)
ticket = controller.find_one(user_id=user_id, id=ticket_id, serialize=True)
return ticket
2 changes: 1 addition & 1 deletion apps/middlewares/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def decorated(*args, **kwargs):
request.user = user
except Exception as error:
raise ValidationError(message=str(error), status_code=401, payload={
'message': 'Invalid token'})
'message': 'AN error occurred when checking credential'})
return f(*args, **kwargs)
return decorated

Expand Down
3 changes: 2 additions & 1 deletion controllers/show_time_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def insert(self, kwargs):

def find(self, id=None, start_date=datetime.now()):
item = f'where st.id ={id}' if id else ''
results = self.db.execute(self.get_query(start_date=start_date, item=item), named=True, commit=True)
results = self.db.execute(self.get_query(
start_date=start_date, item=item), named=True, commit=True)
return results

def get_query(self, item='', start_date=datetime.now()):
Expand Down
8 changes: 5 additions & 3 deletions controllers/sql_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ def update(self, id, record, operator='OR', serialize=False):
query = self.instance.update(id, operator, **record)
return self.dict_to_tuple(self.db.execute(query, True), serialize)

def find(self, operator='OR', serialize=False, joins='', check='=', **kwargs):
query = self.instance.find(operator, joins, check, **kwargs)
def find(self, operator='AND', serialize=False, joins='', params=[], **kwargs):
query = self.instance.find(operator, joins, params)
return self.dict_to_tuple(self.db.execute(query, True), serialize)

def find_one(self, serialize=False, **kwargs):
items = self.find(serialize=serialize, **kwargs)
params = [{'operator': '=', 'value': value, 'field': key}
for key, value in kwargs.items()]
items = self.find(serialize=serialize, params=params)
return items[0] if len(items) > 0 else []

def dict_to_tuple(self, items, serialize):
Expand Down
12 changes: 6 additions & 6 deletions controllers/ticket_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def validate_seat(self, seats, showtime_id):
'message': f"seat numbers '{seat_diff}' in cinema hall not available check available seats for showtime"})
return

def find(self, operator='OR', serialize=False, check='=', ** kwargs):
for item in ['show_date_time', 'movie_id', 'price']:
joins = 'left join showtime on showtime.id = ticket.showtime_id'
if item in kwargs.keys():
kwargs[item] = {'table': 'showtime', 'value': kwargs[item]}
return super().find(operator, serialize, joins, check, **kwargs)
def find(self, operator='OR', serialize=False, params=[], **kwargs):
for index ,item in enumerate(list(params)):
if item.get('field') in ['show_date_time', 'movie_id', 'price']:
params[index]['table'] = 'showtime'
joins = 'left join showtime on showtime.id = ticket.showtime_id'
return super().find(operator, serialize, joins, params)
Loading

0 comments on commit 57c242d

Please sign in to comment.