diff --git a/docs/samples/adv.py b/docs/samples/adv.py new file mode 100644 index 00000000..7cb60fae --- /dev/null +++ b/docs/samples/adv.py @@ -0,0 +1,15 @@ + +from glom import glom + + +class Seq(object): + "Applies a list of specs to a single target, returning a list of results" + def __init__(self, *subspecs): + self.subspecs = subspecs + + def glomit(self, target, scope): + return [scope[glom](target, spec, scope) for spec in self.subspecs] + +output = glom('1', Seq(float, int)) + +assert output == [1.0, 1] diff --git a/docs/samples/intro.py b/docs/samples/intro.py new file mode 100644 index 00000000..052eacaa --- /dev/null +++ b/docs/samples/intro.py @@ -0,0 +1,96 @@ +data = \ +{ + "status": "ok", + "version": "v1", + "sched_date": "2016-05-12", + "table_name": "Outer table", + "data": [ + { + "data": [ + { + "year": "2009", + "values": [ + { + "version": "v1", + "data": [ + { + "data": [ + { + "year": "2009", + "values": [42], + } + ], + "sched_date": "2014-03-18", + "table_name": "Inner Table", + "unit": "Percent" + } + ] + } + ], + } + ], + } + ] +} + +print(data) + +data.get('data', [{}])[0] + +res = data.get('data', [{}])[0].get('data')[0].get('values', [{}])[0].get('data')[0].get('sched_date') +print(res) + + +## The transformation + +from glom import glom + +target = {'ID': 2, 'data': {'isoDate': '1999-01-01'}} + +response = {'id': glom(target, 'ID'), + 'date': glom(target, 'data.isoDate')} + +response = glom(target, {'id': 'ID', + 'date': 'data.isoDate'}) +print(response) + + +def get_response(in_data): + ret = {} + + try: + ret['id'] = in_data['ID'] + except (KeyError, TypeError): + pass # TODO + + try: + data = in_data['data'] + except KeyError: + pass # TODO + + try: + ret['date'] = data['isoDate'] + except (KeyError, TypeError): + pass # TODO + + return ret + +assert get_response(target) == response + + +EMAIL_SPEC = {'id': 'email_id', + 'email': 'email_addr', + 'type': 'email_type'} + + +def get_email(email_id): + email = Email.objects.get(email_id=email_id) + return glom(email, EMAIL_SPEC) + + +def get_all_emails(**filters): + queryset = Email.objects.filter(**filters) + + all_emails_spec = {'results': [EMAIL_SPEC]} + + return glom(queryset, all_emails_spec) diff --git a/docs/samples/mid_specs.py b/docs/samples/mid_specs.py new file mode 100644 index 00000000..be485753 --- /dev/null +++ b/docs/samples/mid_specs.py @@ -0,0 +1,58 @@ +# assignment + +from glom import glom, Assign + +target = {'a': [{'b': 'c'}, {'d': None}]} + +# let's set the 'd' key to 'e' +glom(target, Assign('a.1.d', 'e')) + +assert target['a'][1]['d'] == 'e' + + +from glom import Spec + +# let's assign one target value to another target value +glom(target, Assign('a.1.d', Spec('a.0.b'))) + +assert target['a'] == [{'b': 'c'}, {'d': 'c'}] + + +# Iter + +from glom import glom, Iter + +target = ['1', '2', '1', '3'] + +spec = Iter().map(int).unique().all() + +output = glom(target, spec) +assert output == [1, 2, 3] + +# T + +from glom import glom, T + +target = {'a': {'b': {'c': 'd'}}} + +spec = T['a']['b']['c'] + +output = glom(target, spec) +assert output == 'd' + +output = glom(target, T['a']['b']['c'].upper()) +assert output == 'D' + +output = glom(target, T['z'].upper(), default=None) +assert output is None + + +class Contact(object): + def __init__(self, name, details_dict): + self.name = name + self.details = details_dict + +frank = Contact('Frank', {'emails': ['FRANK@NothingButHotdogs.BIZ']}) + +output = glom(frank, T.details['emails'][0].lower()) +assert output == 'frank@nothingbuthotdogs.biz' diff --git a/glom/test/demo_test.py b/glom/test/demo_test.py new file mode 100644 index 00000000..81514ae1 --- /dev/null +++ b/glom/test/demo_test.py @@ -0,0 +1,81 @@ +""" +structured demo code as tests to keep us honest in ohnly showing working code +""" +import json + +import pytest + +from glom import Ref, Match, Switch, Auto, T, glom, Or, GlomError, M, And + + + +def test_json_check(): + # we can define a self-recursive spec that + # checks a data structure for JSON compatibility + json_spec = Match(Ref( + 'json', Switch({ + dict: {str: Ref('json')}, + list: [Ref('json')], + Or(int, float, str): T, + }) + )) + glom({'customers': ['alice', 'bob']}, json_spec) + # why might we want to do this? + # first, we get a nice error tree + # we also have very fine grained control over behaviors + # maybe we want to be more strict than the standard library + json.dumps({1: 1}) # {"1": 1} + with pytest.raises(GlomError): + glom({1: 1}, json_spec) + # maybe we want to customize a little bit + # for example, lets say we want to ensure that + # integers are 'float-safe' (can be losslessly + # converted to an IEEE 754 double precision float) + json_spec = Match(Ref( + 'json', Switch({ + dict: {str: Ref('json')}, + list: [Ref('json')], + Or(float, str): T, + And(int, M > - 2 ** 52, M < 2 ** 52): T, + }) + )) + glom(2**51, json_spec) + with pytest.raises(GlomError): + glom(2**52, json_spec) + # maybe we want to dump an object but still + # check that its output is valid + class Message(object): + def __init__(self, value): + self.value = value + + def as_dict(self): + return {'type': 'message', 'value': self.value} + + json_spec = Match(Ref( + 'json', Switch({ + dict: {str: Ref('json')}, + list: [Ref('json')], + Or(int, float, str): T, + T.as_dict: Auto((T.as_dict(), Match(Ref('json')))) + }) + )) + + # what would the alternative, self-recursive version look like? + def json_spec_r(val): + if type(val) is dict: + for key in val: + assert isinstance(key, str) + return { + key: json_spec_r(sub_val) for key, sub_val in val.items()} + if type(val) is list: + return [json_spec_r(sub_val) for sub_val in val] + if type(val) in (int, float, str): + return val + if hasattr(val, "as_dict"): + return json_spec_r(val.as_dict()) + raise TypeError('no match') + + glom(Message(1), json_spec) + glom(Message(Message(1)), json_spec) + with pytest.raises(GlomError): + glom(Message(object), json_spec)