SP/web2py/gluon/packages/dal/pydal/helpers/rest.py

317 lines
15 KiB
Python
Raw Permalink Normal View History

2018-10-25 15:33:07 +00:00
from .regex import REGEX_SEARCH_PATTERN, REGEX_SQUARE_BRACKETS
from .._compat import long
def to_num(num):
result = 0
try:
result = long(num)
except NameError as e:
result = int(num)
return result
class RestParser(object):
def __init__(self, db):
self.db = db
def auto_table(self, table, base='', depth=0):
patterns = []
for field in self.db[table].fields:
if base:
tag = '%s/%s' % (base, field.replace('_', '-'))
else:
tag = '/%s/%s' % (
table.replace('_', '-'), field.replace('_', '-'))
f = self.db[table][field]
if not f.readable:
continue
if f.type == 'id' or 'slug' in field or \
f.type.startswith('reference'):
tag += '/{%s.%s}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
elif f.type.startswith('boolean'):
tag += '/{%s.%s}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
elif f.type in ('float', 'double', 'integer', 'bigint'):
tag += '/{%s.%s.ge}/{%s.%s.lt}' % (table, field, table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
elif f.type.startswith('list:'):
tag += '/{%s.%s.contains}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
elif f.type in ('date', 'datetime'):
tag += '/{%s.%s.year}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
tag += '/{%s.%s.month}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
tag += '/{%s.%s.day}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
if f.type in ('datetime', 'time'):
tag += '/{%s.%s.hour}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
tag += '/{%s.%s.minute}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
tag += '/{%s.%s.second}' % (table, field)
patterns.append(tag)
patterns.append(tag+'/:field')
if depth > 0:
for f in self.db[table]._referenced_by:
tag += '/%s[%s.%s]' % (table, f.tablename, f.name)
patterns.append(tag)
patterns += self.auto_table(table, base=tag, depth=depth-1)
return patterns
def parse(self, patterns, args, vars, queries=None, nested_select=True):
"""
Example:
Use as::
db.define_table('person',Field('name'),Field('info'))
db.define_table('pet',
Field('ownedby',db.person),
Field('name'),Field('info')
)
@request.restful()
def index():
def GET(*args,**vars):
patterns = [
"/friends[person]",
"/{person.name}/:field",
"/{person.name}/pets[pet.ownedby]",
"/{person.name}/pets[pet.ownedby]/{pet.name}",
"/{person.name}/pets[pet.ownedby]/{pet.name}/:field",
("/dogs[pet]", db.pet.info=='dog'),
("/dogs[pet]/{pet.name.startswith}", db.pet.info=='dog'),
]
parser = db.parse_as_rest(patterns,args,vars)
if parser.status == 200:
return dict(content=parser.response)
else:
raise HTTP(parser.status,parser.error)
def POST(table_name,**vars):
if table_name == 'person':
return db.person.validate_and_insert(**vars)
elif table_name == 'pet':
return db.pet.validate_and_insert(**vars)
else:
raise HTTP(400)
return locals()
"""
re1 = REGEX_SEARCH_PATTERN
re2 = REGEX_SQUARE_BRACKETS
if patterns == 'auto':
patterns = []
for table in self.db.tables:
if not table.startswith('auth_'):
patterns.append('/%s[%s]' % (table, table))
patterns += self.auto_table(table, base='', depth=1)
else:
i = 0
while i < len(patterns):
pattern = patterns[i]
if not isinstance(pattern, str):
pattern = pattern[0]
tokens = pattern.split('/')
if tokens[-1].startswith(':auto') and re2.match(tokens[-1]):
new_patterns = self.auto_table(
tokens[-1][tokens[-1].find('[')+1:-1],
'/'.join(tokens[:-1]))
patterns = patterns[:i]+new_patterns+patterns[i+1:]
i += len(new_patterns)
else:
i += 1
if '/'.join(args) == 'patterns':
return self.db.Row({
'status': 200, 'pattern': 'list', 'error': None,
'response': patterns})
for pattern in patterns:
basequery, exposedfields = None, []
if isinstance(pattern, tuple):
if len(pattern) == 2:
pattern, basequery = pattern
elif len(pattern) > 2:
pattern, basequery, exposedfields = pattern[0:3]
otable = table = None
if not isinstance(queries, dict):
dbset = self.db(queries)
if basequery is not None:
dbset = dbset(basequery)
i = 0
tags = pattern[1:].split('/')
if len(tags) != len(args):
continue
for tag in tags:
if re1.match(tag):
tokens = tag[1:-1].split('.')
table, field = tokens[0], tokens[1]
if not otable or table == otable:
if len(tokens) == 2 or tokens[2] == 'eq':
query = self.db[table][field] == args[i]
elif tokens[2] == 'ne':
query = self.db[table][field] != args[i]
elif tokens[2] == 'lt':
query = self.db[table][field] < args[i]
elif tokens[2] == 'gt':
query = self.db[table][field] > args[i]
elif tokens[2] == 'ge':
query = self.db[table][field] >= args[i]
elif tokens[2] == 'le':
query = self.db[table][field] <= args[i]
elif tokens[2] == 'year':
query = self.db[table][field].year() == args[i]
elif tokens[2] == 'month':
query = self.db[table][field].month() == args[i]
elif tokens[2] == 'day':
query = self.db[table][field].day() == args[i]
elif tokens[2] == 'hour':
query = self.db[table][field].hour() == args[i]
elif tokens[2] == 'minute':
query = self.db[table][field].minutes() == args[i]
elif tokens[2] == 'second':
query = self.db[table][field].seconds() == args[i]
elif tokens[2] == 'startswith':
query = self.db[table][field].startswith(args[i])
elif tokens[2] == 'contains':
query = self.db[table][field].contains(args[i])
else:
raise RuntimeError("invalid pattern: %s" % pattern)
if len(tokens) == 4 and tokens[3] == 'not':
query = ~query
elif len(tokens) >= 4:
raise RuntimeError("invalid pattern: %s" % pattern)
if not otable and isinstance(queries, dict):
dbset = self.db(queries[table])
if basequery is not None:
dbset = dbset(basequery)
dbset = dbset(query)
else:
raise RuntimeError(
"missing relation in pattern: %s" % pattern)
elif re2.match(tag) and args[i] == tag[:tag.find('[')]:
ref = tag[tag.find('[')+1:-1]
if '.' in ref and otable:
table, field = ref.split('.')
selfld = '_id'
if self.db[table][field].type.startswith('reference '):
refs = [
x.name for x in self.db[otable]
if x.type == self.db[table][field].type]
else:
refs = [
x.name for x in self.db[table]._referenced_by
if x.tablename == otable]
if refs:
selfld = refs[0]
if nested_select:
try:
dbset = self.db(self.db[table][field].belongs(
dbset._select(self.db[otable][selfld])))
except ValueError:
return self.db.Row({
'status': 400, 'pattern': pattern,
'error': 'invalid path', 'response': None})
else:
items = [
item.id for item in dbset.select(
self.db[otable][selfld])
]
dbset = self.db(
self.db[table][field].belongs(items))
else:
table = ref
if not otable and isinstance(queries, dict):
dbset = self.db(queries[table])
dbset = dbset(self.db[table])
elif tag == ':field' and table:
# print 're3:'+tag
field = args[i]
if field not in self.db[table]:
break
# hand-built patterns should respect .readable=False as well
if not self.db[table][field].readable:
return self.db.Row({
'status': 418, 'pattern': pattern,
'error': 'I\'m a teapot', 'response': None})
try:
distinct = vars.get('distinct', False) == 'True'
offset = to_num(vars.get('offset', None) or 0)
limits = (
offset,
to_num(vars.get('limit', None) or 1000) + offset)
except ValueError:
return self.db.Row({
'status': 400, 'error': 'invalid limits',
'response': None})
items = dbset.select(
self.db[table][field], distinct=distinct,
limitby=limits)
if items:
return self.db.Row({
'status': 200, 'response': items,
'pattern': pattern})
else:
return self.db.Row({
'status': 404, 'pattern': pattern,
'error': 'no record found', ' response': None})
elif tag != args[i]:
break
otable = table
i += 1
if i == len(tags) and table:
if hasattr(self.db[table], '_id'):
ofields = vars.get(
'order', self.db[table]._id.name).split('|')
else:
ofields = vars.get(
'order', self.db[table]._primarykey[0]).split('|')
try:
orderby = [
self.db[table][f] if not f.startswith('~')
else ~self.db[table][f[1:]] for f in ofields]
except (KeyError, AttributeError):
return self.db.Row({
'status': 400, 'error': 'invalid orderby',
'response': None})
if exposedfields:
fields = [
field for field in self.db[table]
if str(field).split('.')[-1] in exposedfields and
field.readable]
else:
fields = [
field for field in self.db[table]
if field.readable]
count = dbset.count()
try:
offset = to_num(vars.get('offset', None) or 0)
limits = (
offset,
to_num(vars.get('limit', None) or 1000) + offset)
except ValueError:
return self.db.Row({
'status': 400, 'error': ' invalid limits',
'response': None})
try:
response = dbset.select(
limitby=limits, orderby=orderby, *fields)
except ValueError:
return self.db.Row({
'status': 400, 'pattern': pattern,
'error': 'invalid path', 'response': None})
return self.db.Row({
'status': 200, 'response': response,
'pattern': pattern, 'count': count})
return self.db.Row({
'status': 400, 'error': 'no matching pattern', 'response': None})