Skip to content

Instantly share code, notes, and snippets.

@anfedorov
Created July 3, 2014 15:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anfedorov/f91bd2118d8a381ea7ea to your computer and use it in GitHub Desktop.
Save anfedorov/f91bd2118d8a381ea7ea to your computer and use it in GitHub Desktop.
import re
from collections import namedtuple
from decimal import Decimal
from dateutil import parser
from functools import partial
token_parsers = {
'decimal': Decimal,
'payee': str,
'str': str,
'float': float,
'int': int,
'isodate': lambda x: parser.parse(x).date(),
'any': lambda x: x,
}
def parse(schema, x=None):
if x is None:
return partial(parse, schema)
schema = schema.strip()
if schema == '':
return None
if schema in token_parsers:
return token_parsers[schema](x)
if schema.startswith('{') and schema.endswith('}'): # is dict
m = re.match(r'^{\s*(.+)\s*:\s*(.+)\s*,\s*\.\.\.\s*}$', schema)
if m: # is repeating
assert isinstance(x, dict), 'not dict: %s' % x
return {parse(m.group(1), k): parse(m.group(2), v) for k, v in x.items()}
else: # is specific
return parse_object(schema[1:-1], x)
if schema.startswith('[') and schema.endswith(']'): # is list
m = re.match(r'^\[(.*),\s*\.\.\.\s*\]$', schema)
if m: # is repeating
assert isinstance(x, list), 'not list: %s' % x
return [parse(m.group(1), v) for v in x]
else: # is optional
assert False, 'list schema must end in ", ..."'
if schema.startswith('(') and schema.endswith(')'): # is tuple
schema_parts = schema[1:-1].split(',')
assert len(schema_parts) == len(x), 'bad number of parts: %s' % x
return tuple(parse(s, v) for s, v in zip(schema_parts, x))
m = re.match(r'^(\w+)\((.*)\)$', schema) # is namedtuple
if m:
param = namedtuple('param', ['name', 'type'])
params = [param(*p.split(':')) for p in m.group(2).split(',')]
assert len(params) == len(x), 'bad len: %s' % x
nt = namedtuple(m.group(1), [p.name.strip() for p in params])
return nt(*[parse(p.type.strip(), v) for p, v in zip(params, x)])
assert False, 'what is this i dont even: "%s"' % schema
def find_closing(string, open_index):
open_char = string[open_index]
assert open_char in '{(['
close_char = '}' if open_char == '{' else \
')' if open_char == '(' else \
']'
depth = 1
index = open_index + 1
while depth > 0:
if string[index] == open_char:
depth += 1
elif string[index] == close_char:
depth -= 1
index += 1
return index
def find_comma(string, index):
while string[index] != ',':
if string[index] in '{([':
index = find_closing(string, index)
else:
index += 1
return index
def parse_object(schema, o):
schema = schema.strip()
if not schema:
return {}
retval = {}
m = re.match(r'^(\'([^\']+)\':\s*)(.*?),', schema, flags=re.S) or re.match(r'^"([^"]+)":\s*(.)', schema)
assert m, 'bad literal object: %s' % schema
key = m.group(2)
assert key in o, 'key %s not found in %s' % (key, o)
value_start = len(m.group(1))
if schema[value_start] in '([{':
value_end = find_closing(schema, value_start)
retval[key] = parse(schema[value_start:value_end], o[key])
else:
value_end = find_comma(schema, value_start)
retval[key] = parse(schema[value_start:value_end], o[key])
retval.update(parse_object(schema[value_end+1:], o))
return retval
print parse('isodate', '2014-01-01')
print parse('[isodate, ...]', ['2014-01-01', '2014-02-01'])
print parse('{payee: decimal, ...}', {'one': 1, 'two': 2})
print parse('(str, str)', ('foo', 'bar'))
print parse('Point(x:float, y:float)', (1, 2))
schema = """
{
'foo': [isodate, ...],
'bar': {
payee: decimal,
...
},
'baz': Player(name:str, score:int),
}
"""
obj = {
'foo': ['2014-01-01', '2014-02-01'],
'bar': {
'p1': 3,
'p2': 2,
'p3': 1,
},
'baz': ('bob', 10),
}
print parse(schema, obj)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment