/gdata/core.py
Python | 275 lines | 238 code | 10 blank | 27 comment | 0 complexity | b89bce7bc5058ce014750831f9e9f313 MD5 | raw file
1#!/usr/bin/env python 2# 3# Copyright (C) 2010 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17 18# This module is used for version 2 of the Google Data APIs. 19 20 21__author__ = 'j.s@google.com (Jeff Scudder)' 22 23 24"""Provides classes and methods for working with JSON-C. 25 26This module is experimental and subject to backwards incompatible changes. 27 28 Jsonc: Class which represents JSON-C data and provides pythonic member 29 access which is a bit cleaner than working with plain old dicts. 30 parse_json: Converts a JSON-C string into a Jsonc object. 31 jsonc_to_string: Converts a Jsonc object into a string of JSON-C. 32""" 33 34 35try: 36 import simplejson 37except ImportError: 38 try: 39 # Try to import from django, should work on App Engine 40 from django.utils import simplejson 41 except ImportError: 42 # Should work for Python2.6 and higher. 43 import json as simplejson 44 45 46def _convert_to_jsonc(x): 47 """Builds a Jsonc objects which wraps the argument's members.""" 48 49 if isinstance(x, dict): 50 jsonc_obj = Jsonc() 51 # Recursively transform all members of the dict. 52 # When converting a dict, we do not convert _name items into private 53 # Jsonc members. 54 for key, value in x.iteritems(): 55 jsonc_obj._dict[key] = _convert_to_jsonc(value) 56 return jsonc_obj 57 elif isinstance(x, list): 58 # Recursively transform all members of the list. 59 members = [] 60 for item in x: 61 members.append(_convert_to_jsonc(item)) 62 return members 63 else: 64 # Return the base object. 65 return x 66 67 68def parse_json(json_string): 69 """Converts a JSON-C string into a Jsonc object. 70 71 Args: 72 json_string: str or unicode The JSON to be parsed. 73 74 Returns: 75 A new Jsonc object. 76 """ 77 78 return _convert_to_jsonc(simplejson.loads(json_string)) 79 80 81def jsonc_to_string(jsonc_obj): 82 """Converts a Jsonc object into a string of JSON-C.""" 83 84 return simplejson.dumps(_convert_to_object(jsonc_obj)) 85 86 87def prettify_jsonc(jsonc_obj, indentation=2): 88 """Converts a Jsonc object to a pretified (intented) JSON string.""" 89 90 return simplejson.dumps(_convert_to_object(jsonc_obj), indent=indentation) 91 92 93 94def _convert_to_object(jsonc_obj): 95 """Creates a new dict or list which has the data in the Jsonc object. 96 97 Used to convert the Jsonc object to a plain old Python object to simplify 98 conversion to a JSON-C string. 99 100 Args: 101 jsonc_obj: A Jsonc object to be converted into simple Python objects 102 (dicts, lists, etc.) 103 104 Returns: 105 Either a dict, list, or other object with members converted from Jsonc 106 objects to the corresponding simple Python object. 107 """ 108 109 if isinstance(jsonc_obj, Jsonc): 110 plain = {} 111 for key, value in jsonc_obj._dict.iteritems(): 112 plain[key] = _convert_to_object(value) 113 return plain 114 elif isinstance(jsonc_obj, list): 115 plain = [] 116 for item in jsonc_obj: 117 plain.append(_convert_to_object(item)) 118 return plain 119 else: 120 return jsonc_obj 121 122 123def _to_jsonc_name(member_name): 124 """Converts a Python style member name to a JSON-C style name. 125 126 JSON-C uses camelCaseWithLower while Python tends to use 127 lower_with_underscores so this method converts as follows: 128 129 spam becomes spam 130 spam_and_eggs becomes spamAndEggs 131 132 Args: 133 member_name: str or unicode The Python syle name which should be 134 converted to JSON-C style. 135 136 Returns: 137 The JSON-C style name as a str or unicode. 138 """ 139 140 characters = [] 141 uppercase_next = False 142 for character in member_name: 143 if character == '_': 144 uppercase_next = True 145 elif uppercase_next: 146 characters.append(character.upper()) 147 uppercase_next = False 148 else: 149 characters.append(character) 150 return ''.join(characters) 151 152 153class Jsonc(object): 154 """Represents JSON-C data in an easy to access object format. 155 156 To access the members of a JSON structure which looks like this: 157 { 158 "data": { 159 "totalItems": 800, 160 "items": [ 161 { 162 "content": { 163 "1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp" 164 }, 165 "viewCount": 220101, 166 "commentCount": 22, 167 "favoriteCount": 201 168 } 169 ] 170 }, 171 "apiVersion": "2.0" 172 } 173 174 You would do the following: 175 x = gdata.core.parse_json(the_above_string) 176 # Gives you 800 177 x.data.total_items 178 # Should be 22 179 x.data.items[0].comment_count 180 # The apiVersion is '2.0' 181 x.api_version 182 183 To create a Jsonc object which would produce the above JSON, you would do: 184 gdata.core.Jsonc( 185 api_version='2.0', 186 data=gdata.core.Jsonc( 187 total_items=800, 188 items=[ 189 gdata.core.Jsonc( 190 view_count=220101, 191 comment_count=22, 192 favorite_count=201, 193 content={ 194 '1': ('rtsp://v5.cache3.c.youtube.com' 195 '/CiILENy.../0/0/0/video.3gp')})])) 196 or 197 x = gdata.core.Jsonc() 198 x.api_version = '2.0' 199 x.data = gdata.core.Jsonc() 200 x.data.total_items = 800 201 x.data.items = [] 202 # etc. 203 204 How it works: 205 The JSON-C data is stored in an internal dictionary (._dict) and the 206 getattr, setattr, and delattr methods rewrite the name which you provide 207 to mirror the expected format in JSON-C. (For more details on name 208 conversion see _to_jsonc_name.) You may also access members using 209 getitem, setitem, delitem as you would for a dictionary. For example 210 x.data.total_items is equivalent to x['data']['totalItems'] 211 (Not all dict methods are supported so if you need something other than 212 the item operations, then you will want to use the ._dict member). 213 214 You may need to use getitem or the _dict member to access certain 215 properties in cases where the JSON-C syntax does not map neatly to Python 216 objects. For example the YouTube Video feed has some JSON like this: 217 "content": {"1": "rtsp://v5.cache3.c.youtube.com..."...} 218 You cannot do x.content.1 in Python, so you would use the getitem as 219 follows: 220 x.content['1'] 221 or you could use the _dict member as follows: 222 x.content._dict['1'] 223 224 If you need to create a new object with such a mapping you could use. 225 226 x.content = gdata.core.Jsonc(_dict={'1': 'rtsp://cache3.c.youtube.com...'}) 227 """ 228 229 def __init__(self, _dict=None, **kwargs): 230 json = _dict or {} 231 for key, value in kwargs.iteritems(): 232 if key.startswith('_'): 233 object.__setattr__(self, key, value) 234 else: 235 json[_to_jsonc_name(key)] = _convert_to_jsonc(value) 236 237 object.__setattr__(self, '_dict', json) 238 239 def __setattr__(self, name, value): 240 if name.startswith('_'): 241 object.__setattr__(self, name, value) 242 else: 243 object.__getattribute__( 244 self, '_dict')[_to_jsonc_name(name)] = _convert_to_jsonc(value) 245 246 def __getattr__(self, name): 247 if name.startswith('_'): 248 object.__getattribute__(self, name) 249 else: 250 try: 251 return object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] 252 except KeyError: 253 raise AttributeError( 254 'No member for %s or [\'%s\']' % (name, _to_jsonc_name(name))) 255 256 257 def __delattr__(self, name): 258 if name.startswith('_'): 259 object.__delattr__(self, name) 260 else: 261 try: 262 del object.__getattribute__(self, '_dict')[_to_jsonc_name(name)] 263 except KeyError: 264 raise AttributeError( 265 'No member for %s (or [\'%s\'])' % (name, _to_jsonc_name(name))) 266 267 # For container methods pass-through to the underlying dict. 268 def __getitem__(self, key): 269 return self._dict[key] 270 271 def __setitem__(self, key, value): 272 self._dict[key] = value 273 274 def __delitem__(self, key): 275 del self._dict[key]