Skip to content

Instantly share code, notes, and snippets.

@blast-hardcheese
Last active August 5, 2020 20:52
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 blast-hardcheese/494ae43635bd8b68e4a3f5db26c72e71 to your computer and use it in GitHub Desktop.
Save blast-hardcheese/494ae43635bd8b68e4a3f5db26c72e71 to your computer and use it in GitHub Desktop.
composable structure decoders for Python
def foo_api(cluster, realm, path, decode={200: lambda x: x}):
url='https://{cluster}.{realm}.example.com/{path}'.format(
cluster=cluster,
realm=realm,
path=path,
)
resp = requests.get(url, auth=(creds.user, creds.password))
return decode[resp.status_code](resp.content)
Baz = namedtuple('Baz', ['a', 'b', 'c'])
decode_baz = decode_object(Baz)()
Bar = namedtuple('Bar', ['baz'])
decode_bar = decode_object(Bar)(baz=decode_baz)
BarNotFound = namedtuple('BarNotFound', [])
def get_bar(cluster, realm, id):
return (
foo_api(cluster, realm, 'v1/api/{id}.json'.format(id=id),
{ 200: decode_json(decode_bar),
404: decode_const(BarNotFound()),
},
)
)
from collections import namedtuple
def decode_array(decoder):
'''Successively apply decoder over an entire array'''
func = lambda xs: [decoder(x) for x in xs]
func.__name__ = 'decode_array({})'.format(decoder.__name__)
return func
def decode_object(decoder):
'''
Foo = namedtuple('Foo', ['bar', 'baz'])
decode_foo = decode_object(Foo)(bar=int) # baz is not transformed, passed through as-is
decode_foo({'bar': '5', 'baz': 'blix'}) # Foo(bar=5, baz='blix')
'''
def inner(**fields):
__name__ = 'decode_object({})'.format(decoder.__name__)
def func(obj):
try:
return decoder(**{
k: fields[k](v) if k in fields else v
for k,v
in obj.items()
})
except Exception as e:
raise Exception('Error while applying {}: {}'.format(__name__, e))
func.__name__ = __name__
return func
return inner
Foo = namedtuple('Foo', ['bar', 'baz'])
decode_foo = decode_object(Foo)(bar=int) # baz is not transformed, passed through as-is
>>> decode_foo({'bar': '1', 'baz': 'a'})
Foo(bar=1, baz='a') # Decode a single structure
>>> type(decode_foo({'bar': '1', 'baz': 'a'}).bar) # Types work seamlessly
<class 'int'>
>>> decode_array(decode_foo)([{'bar': '1', 'baz': 'a'}, {'bar': '2', 'baz': 'b'}])
[Foo(bar=1, baz='a'), Foo(bar=2, baz='b')] # Decoders compose to build larger structures
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment