├── .gitignore ├── readme.md ├── setup.py ├── test.py └── typify ├── __init__.py ├── helpers.py ├── matchers.py └── typify.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | env 3 | dist 4 | MANIFEST 5 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Basic Usage 2 | ---- 3 | class Record(typify.Model): 4 | id = typify.IntegerMatcher() 5 | name = typify.StringMatcher()) 6 | 7 | record_json = "{\"id\":1,\"name\":\"testname\"} 8 | 9 | a_record = Record.from_json(record_json) 10 | print record.id 11 | print record.name 12 | print a_record.to_json() 13 | 14 | This library's objective is to aid the creation of objects from common text based representation formats (JSON, YAML and Python Dictionaries). 15 | The following response will be used for a common example: 16 | 17 | { 18 | "id":1, 19 | "name":"Gold Start Ablum", 20 | "postal_code":44024, 21 | "created_at":"2015-04-24T21:51:50.691473", 22 | "songs":[ 23 | { 24 | "id":1, 25 | "name":"songname" 26 | } 27 | ], 28 | "author":{ 29 | "id":1, 30 | "name":"test author", 31 | "info":{ 32 | "id":1, 33 | "email":"test@example.com" 34 | } 35 | }, 36 | "_links":{ 37 | "self":"testselflink", 38 | "next":"testnextlink", 39 | "embedded":{ 40 | "embedded_link":"testembeddedlink" 41 | } 42 | }, 43 | "list_thing":[ 44 | 1, 45 | 2, 46 | 3, 47 | 4 48 | ] 49 | } 50 | 51 | 52 | Supported Formats 53 | --- 54 | * from_json 55 | * from_yaml 56 | * from_dict 57 | 58 | * to_json 59 | * to_yaml 60 | * to_dict 61 | 62 | 63 | 64 | Renaming Attributes 65 | --- 66 | class Record(typify.Model): 67 | zip = typify.IntegerMatcher(key='postal_code') 68 | 69 | Parsing Attributes into complex type 70 | --- 71 | class Record(typify.Model): 72 | created_at = typify.StringMatcher(parse=lambda x: datetime.datetime.strptime(x, '%Y-%m-%dT%H:%M:%S.%f')) 73 | 74 | Merging parts of the document 75 | --- 76 | class Record(typify.Model): 77 | merged_stuff = typify.StringMatcher(merge=lambda doc: str(doc['id']) + ':' + doc['name'] ) 78 | 79 | Converting complex type when writing to JSON 80 | --- 81 | class Record(typify.Model): 82 | created_at = typify.StringMatcher(convert=lambda x: x.isoformat(), default=lambda: datetime.datetime.now()) 83 | 84 | Wrapped attributes 85 | --- 86 | class Record(typify.Model): 87 | self = typify.StringMatcher(wrap='_links') 88 | embedded_link = typify.StringMatcher(wrap=['_links', 'embedded']) 89 | 90 | Nested Objects 91 | --- 92 | 93 | class Author(typify.Model): 94 | id = typify.IntegerMatcher() 95 | name = typify.StringMatcher() 96 | 97 | class Record(typify.Model): 98 | author = typify.ObjectMatcher(Author) 99 | 100 | Objects can be nested forever and ever 101 | --- 102 | class AuthorInfo(typify.Model): 103 | id = typify.IntegerMatcher() 104 | email = typify.StringMatcher() 105 | 106 | class Author(typify.Model): 107 | id = typify.IntegerMatcher() 108 | name = typify.StringMatcher() 109 | info = typify.ObjectMatcher(AuthorInfo) 110 | 111 | class Record(typify.Model): 112 | author = typify.ObjectMatcher(Author) 113 | 114 | Collections of objects 115 | --- 116 | class Song(typify.Model): 117 | id = typify.IntegerMatcher() 118 | name = typify.StringMatcher() 119 | 120 | class Record(typify.Model): 121 | songs = typify.CollectionMatcher(Song) 122 | 123 | List of basic types 124 | --- 125 | class Record(typify.Model): 126 | list_thing = typify.ListMatcher() 127 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup(name='typify', 4 | version='0.2', 5 | url='https://github.com/MattyO/typify', 6 | description="converts api responses to classes", 7 | author='Matt ODonnell', 8 | author_email='odonnell004@gmail.com', 9 | packages=['typify'], 10 | ) 11 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import typify 3 | import pprint 4 | 5 | 6 | class Song(typify.Model): 7 | id = typify.IntegerMatcher() 8 | name = typify.StringMatcher() 9 | 10 | class AuthorInfo(typify.Model): 11 | id = typify.IntegerMatcher() 12 | email = typify.StringMatcher() 13 | 14 | class Author(typify.Model): 15 | id = typify.IntegerMatcher() 16 | name = typify.StringMatcher() 17 | info = typify.ObjectMatcher(AuthorInfo) 18 | 19 | class Record(typify.Model): 20 | id = typify.IntegerMatcher() 21 | name = typify.StringMatcher() 22 | merged_stuff = typify.StringMatcher(merge=lambda doc: str(doc['id']) + ':' + doc['name'] ) 23 | zip = typify.IntegerMatcher(key='postal_code') 24 | created_at = typify.StringMatcher(parse=lambda x: datetime.datetime.strptime(x, '%Y-%m-%dT%H:%M:%S.%f'), convert=lambda x: x.isoformat(), default=lambda: datetime.datetime.now()) 25 | songs = typify.CollectionMatcher(Song) 26 | author = typify.ObjectMatcher(Author) 27 | self = typify.StringMatcher(wrap='_links') 28 | next = typify.StringMatcher(wrap='_links') 29 | embedded_link = typify.StringMatcher(wrap=['_links', 'embedded']) 30 | list_thing = typify.ListMatcher() 31 | 32 | 33 | #record=Record.from_json("{\"id\":1,\"name\":\"testname\",\"postal_code\":44024,\"created_at\":\"2015-04-24T21:51:50.691473\",\"songs\":[{\"id\":1,\"name\":\"songname\"}],\"author\":{ \"id\": 1, \"name\": \"test author\", \"info\":{\"id\":1, \"email\":\"test@example.com\"}}, \"_links\":{\"self\":\"testselflink\",\"next\":\"testnextlink\",\"embedded\":{\"embedded_link\":\"testembeddedlink\"}}, \"list_thing\":[1,2,3,4]}") 34 | 35 | printer = pprint.PrettyPrinter() 36 | 37 | #print 'start of the real test' 38 | print Record().to_json() 39 | 40 | #record = Record() 41 | #record = Record.from_json("{\"id\":1}") 42 | #print record.to_json() 43 | 44 | #printer.pprint(record.to_dict()) 45 | #print record.to_json() 46 | -------------------------------------------------------------------------------- /typify/__init__.py: -------------------------------------------------------------------------------- 1 | from typify import Model 2 | from matchers import IntegerMatcher, StringMatcher, ListMatcher, CollectionMatcher, ObjectMatcher 3 | -------------------------------------------------------------------------------- /typify/helpers.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | def find_in(key, the_object, wrapped_in, default=None): 4 | 5 | if not wrapped_in: 6 | return the_object.get(key, default) 7 | 8 | wrapped_in = copy.copy(wrapped_in) 9 | 10 | if isinstance(wrapped_in, basestring): #helps out the recursivness of the function 11 | wrapped_in = [wrapped_in] 12 | 13 | temp_wrapped_in = wrapped_in.pop(0) 14 | if not the_object.has_key(temp_wrapped_in): 15 | return default 16 | 17 | return find_in(key, the_object[temp_wrapped_in], wrapped_in) 18 | 19 | def set_or_create(key, value, the_object, wrapped_in): 20 | 21 | if not wrapped_in: 22 | the_object[key] = value 23 | return 24 | 25 | wrapped_in = copy.copy(wrapped_in) 26 | 27 | if isinstance(wrapped_in, basestring): 28 | wrapped_in = [wrapped_in] 29 | 30 | new_key = wrapped_in.pop(0) 31 | if new_key not in the_object: 32 | the_object[new_key] = {} 33 | 34 | return set_or_create(key, value, the_object[new_key], wrapped_in) 35 | 36 | -------------------------------------------------------------------------------- /typify/matchers.py: -------------------------------------------------------------------------------- 1 | class Matcher(object): 2 | 3 | def __init__(self, *args, **kwargs): 4 | if 'key' in kwargs: 5 | self.key = kwargs['key'] 6 | 7 | self.wrap = kwargs.get('wrap', None) 8 | 9 | if 'parse' in kwargs: 10 | self.parse = kwargs['parse'] 11 | 12 | if 'convert' in kwargs: 13 | self.convert = kwargs['convert'] 14 | 15 | if 'merge' in kwargs: 16 | self.merge = kwargs['merge'] 17 | 18 | 19 | self._default = kwargs.get('default', lambda: None) 20 | 21 | def parse(self, value): 22 | return value 23 | 24 | def convert(self, value): 25 | return value 26 | 27 | def default(self): 28 | return self._default() 29 | 30 | class IntegerMatcher(Matcher): 31 | pass 32 | 33 | class StringMatcher(Matcher): 34 | pass 35 | 36 | class ListMatcher(Matcher): 37 | pass 38 | 39 | class CollectionMatcher(Matcher): 40 | 41 | def __init__(self, matcher_cls, **kwargs): 42 | self.matcher_model = matcher_cls 43 | super(CollectionMatcher, self).__init__(**kwargs) 44 | 45 | def parse(self, value): 46 | return [ self.matcher_model.from_dict(inner_dict) for inner_dict in value ] 47 | 48 | def convert(self, value): 49 | return [ a_model.to_dict() for a_model in value ] 50 | 51 | def default(self): 52 | return [] 53 | 54 | class ObjectMatcher(Matcher): 55 | 56 | def __init__(self, matcher_cls, **kwargs): 57 | self.matcher_model = matcher_cls 58 | super(ObjectMatcher, self).__init__(**kwargs) 59 | 60 | def parse(self, value): 61 | return self.matcher_model.from_dict(value) 62 | 63 | def convert(self, value): 64 | return value.to_dict() 65 | 66 | def default(self): 67 | #print 'object matcher default' 68 | m = self.matcher_model() 69 | #print m 70 | return m 71 | #return self.matcher_model() 72 | -------------------------------------------------------------------------------- /typify/typify.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import json 3 | 4 | import matchers 5 | import helpers 6 | 7 | class MetaModel(type): 8 | def __new__(meta, name, bases, namespace): 9 | matchers = MetaModel.find_matchers(namespace) 10 | for key, matcher in matchers: 11 | namespace[key] = matcher.default() 12 | namespace['_meta'] = matchers 13 | 14 | return super(MetaModel, meta).__new__(meta, name, bases, namespace) 15 | 16 | @classmethod 17 | def find_matchers(cls, n): 18 | return [(key, value) for key, value in n.items() if isinstance(value, matchers.Matcher)] 19 | 20 | class Model(object): 21 | 22 | __metaclass__ = MetaModel 23 | 24 | @classmethod 25 | def matchers(cls): 26 | return cls._meta 27 | 28 | @classmethod 29 | def from_dict(cls, a_dict): 30 | inst = cls.__new__(cls) 31 | attributes = {} 32 | matchers = cls.matchers() 33 | 34 | for attribute, matcher in matchers: 35 | key = getattr(matcher, 'key', attribute) 36 | value = helpers.find_in(key, a_dict, matcher.wrap, matcher.default()) 37 | if not isinstance(value, cls): 38 | if hasattr(matcher, 'merge') and callable(matcher.merge): 39 | value = matcher.merge(a_dict) 40 | else: 41 | value = matcher.parse(value) 42 | setattr(inst, attribute, value) 43 | 44 | inst.__init__() 45 | return inst 46 | 47 | @classmethod 48 | def from_json(cls, payload): 49 | json_object = json.loads(payload) 50 | return cls.from_dict(json_object) 51 | 52 | def to_dict(self): 53 | returned_dict = {} 54 | for attribute, matcher in self.matchers(): 55 | key = getattr(matcher, 'key', attribute) 56 | value = matcher.convert(getattr(self, attribute)) 57 | helpers.set_or_create(key, value, returned_dict, matcher.wrap) 58 | 59 | return returned_dict 60 | 61 | def to_json(self): 62 | return json.dumps(self.to_dict()) 63 | --------------------------------------------------------------------------------