280 lines
8.0 KiB
Python
280 lines
8.0 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# Copyright (C) 2010 Google Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
# This module is used for version 2 of the Google Data APIs.
|
|
|
|
|
|
__author__ = 'j.s@google.com (Jeff Scudder)'
|
|
|
|
|
|
"""Provides classes and methods for working with JSON-C.
|
|
|
|
This module is experimental and subject to backwards incompatible changes.
|
|
|
|
Jsonc: Class which represents JSON-C data and provides pythonic member
|
|
access which is a bit cleaner than working with plain old dicts.
|
|
parse_json: Converts a JSON-C string into a Jsonc object.
|
|
jsonc_to_string: Converts a Jsonc object into a string of JSON-C.
|
|
"""
|
|
|
|
|
|
try:
|
|
import simplejson
|
|
except ImportError:
|
|
try:
|
|
# Try to import from django, should work on App Engine
|
|
from django.utils import simplejson
|
|
except ImportError:
|
|
# Should work for Python2.6 and higher.
|
|
import json as simplejson
|
|
|
|
|
|
def _convert_to_jsonc(x):
|
|
"""Builds a Jsonc objects which wraps the argument's members."""
|
|
|
|
if isinstance(x, dict):
|
|
jsonc_obj = Jsonc()
|
|
# Recursively transform all members of the dict.
|
|
# When converting a dict, we do not convert _name items into private
|
|
# Jsonc members.
|
|
for key, value in x.iteritems():
|
|
jsonc_obj._dict[key] = _convert_to_jsonc(value)
|
|
return jsonc_obj
|
|
elif isinstance(x, list):
|
|
# Recursively transform all members of the list.
|
|
members = []
|
|
for item in x:
|
|
members.append(_convert_to_jsonc(item))
|
|
return members
|
|
else:
|
|
# Return the base object.
|
|
return x
|
|
|
|
|
|
def parse_json(json_string):
|
|
"""Converts a JSON-C string into a Jsonc object.
|
|
|
|
Args:
|
|
json_string: str or unicode The JSON to be parsed.
|
|
|
|
Returns:
|
|
A new Jsonc object.
|
|
"""
|
|
|
|
return _convert_to_jsonc(simplejson.loads(json_string))
|
|
|
|
|
|
def parse_json_file(json_file):
|
|
return _convert_to_jsonc(simplejson.load(json_file))
|
|
|
|
|
|
def jsonc_to_string(jsonc_obj):
|
|
"""Converts a Jsonc object into a string of JSON-C."""
|
|
|
|
return simplejson.dumps(_convert_to_object(jsonc_obj))
|
|
|
|
|
|
def prettify_jsonc(jsonc_obj, indentation=2):
|
|
"""Converts a Jsonc object to a pretified (intented) JSON string."""
|
|
|
|
return simplejson.dumps(_convert_to_object(jsonc_obj), indent=indentation)
|
|
|
|
|
|
|
|
def _convert_to_object(jsonc_obj):
|
|
"""Creates a new dict or list which has the data in the Jsonc object.
|
|
|
|
Used to convert the Jsonc object to a plain old Python object to simplify
|
|
conversion to a JSON-C string.
|
|
|
|
Args:
|
|
jsonc_obj: A Jsonc object to be converted into simple Python objects
|
|
(dicts, lists, etc.)
|
|
|
|
Returns:
|
|
Either a dict, list, or other object with members converted from Jsonc
|
|
objects to the corresponding simple Python object.
|
|
"""
|
|
|
|
if isinstance(jsonc_obj, Jsonc):
|
|
plain = {}
|
|
for key, value in jsonc_obj._dict.iteritems():
|
|
plain[key] = _convert_to_object(value)
|
|
return plain
|
|
elif isinstance(jsonc_obj, list):
|
|
plain = []
|
|
for item in jsonc_obj:
|
|
plain.append(_convert_to_object(item))
|
|
return plain
|
|
else:
|
|
return jsonc_obj
|
|
|
|
|
|
def _to_jsonc_name(member_name):
|
|
"""Converts a Python style member name to a JSON-C style name.
|
|
|
|
JSON-C uses camelCaseWithLower while Python tends to use
|
|
lower_with_underscores so this method converts as follows:
|
|
|
|
spam becomes spam
|
|
spam_and_eggs becomes spamAndEggs
|
|
|
|
Args:
|
|
member_name: str or unicode The Python syle name which should be
|
|
converted to JSON-C style.
|
|
|
|
Returns:
|
|
The JSON-C style name as a str or unicode.
|
|
"""
|
|
|
|
characters = []
|
|
uppercase_next = False
|
|
for character in member_name:
|
|
if character == '_':
|
|
uppercase_next = True
|
|
elif uppercase_next:
|
|
characters.append(character.upper())
|
|
uppercase_next = False
|
|
else:
|
|
characters.append(character)
|
|
return ''.join(characters)
|
|
|
|
|
|
class Jsonc(object):
|
|
"""Represents JSON-C data in an easy to access object format.
|
|
|
|
To access the members of a JSON structure which looks like this:
|
|
{
|
|
"data": {
|
|
"totalItems": 800,
|
|
"items": [
|
|
{
|
|
"content": {
|
|
"1": "rtsp://v5.cache3.c.youtube.com/CiILENy.../0/0/0/video.3gp"
|
|
},
|
|
"viewCount": 220101,
|
|
"commentCount": 22,
|
|
"favoriteCount": 201
|
|
}
|
|
]
|
|
},
|
|
"apiVersion": "2.0"
|
|
}
|
|
|
|
You would do the following:
|
|
x = gdata.core.parse_json(the_above_string)
|
|
# Gives you 800
|
|
x.data.total_items
|
|
# Should be 22
|
|
x.data.items[0].comment_count
|
|
# The apiVersion is '2.0'
|
|
x.api_version
|
|
|
|
To create a Jsonc object which would produce the above JSON, you would do:
|
|
gdata.core.Jsonc(
|
|
api_version='2.0',
|
|
data=gdata.core.Jsonc(
|
|
total_items=800,
|
|
items=[
|
|
gdata.core.Jsonc(
|
|
view_count=220101,
|
|
comment_count=22,
|
|
favorite_count=201,
|
|
content={
|
|
'1': ('rtsp://v5.cache3.c.youtube.com'
|
|
'/CiILENy.../0/0/0/video.3gp')})]))
|
|
or
|
|
x = gdata.core.Jsonc()
|
|
x.api_version = '2.0'
|
|
x.data = gdata.core.Jsonc()
|
|
x.data.total_items = 800
|
|
x.data.items = []
|
|
# etc.
|
|
|
|
How it works:
|
|
The JSON-C data is stored in an internal dictionary (._dict) and the
|
|
getattr, setattr, and delattr methods rewrite the name which you provide
|
|
to mirror the expected format in JSON-C. (For more details on name
|
|
conversion see _to_jsonc_name.) You may also access members using
|
|
getitem, setitem, delitem as you would for a dictionary. For example
|
|
x.data.total_items is equivalent to x['data']['totalItems']
|
|
(Not all dict methods are supported so if you need something other than
|
|
the item operations, then you will want to use the ._dict member).
|
|
|
|
You may need to use getitem or the _dict member to access certain
|
|
properties in cases where the JSON-C syntax does not map neatly to Python
|
|
objects. For example the YouTube Video feed has some JSON like this:
|
|
"content": {"1": "rtsp://v5.cache3.c.youtube.com..."...}
|
|
You cannot do x.content.1 in Python, so you would use the getitem as
|
|
follows:
|
|
x.content['1']
|
|
or you could use the _dict member as follows:
|
|
x.content._dict['1']
|
|
|
|
If you need to create a new object with such a mapping you could use.
|
|
|
|
x.content = gdata.core.Jsonc(_dict={'1': 'rtsp://cache3.c.youtube.com...'})
|
|
"""
|
|
|
|
def __init__(self, _dict=None, **kwargs):
|
|
json = _dict or {}
|
|
for key, value in kwargs.iteritems():
|
|
if key.startswith('_'):
|
|
object.__setattr__(self, key, value)
|
|
else:
|
|
json[_to_jsonc_name(key)] = _convert_to_jsonc(value)
|
|
|
|
object.__setattr__(self, '_dict', json)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name.startswith('_'):
|
|
object.__setattr__(self, name, value)
|
|
else:
|
|
object.__getattribute__(
|
|
self, '_dict')[_to_jsonc_name(name)] = _convert_to_jsonc(value)
|
|
|
|
def __getattr__(self, name):
|
|
if name.startswith('_'):
|
|
object.__getattribute__(self, name)
|
|
else:
|
|
try:
|
|
return object.__getattribute__(self, '_dict')[_to_jsonc_name(name)]
|
|
except KeyError:
|
|
raise AttributeError(
|
|
'No member for %s or [\'%s\']' % (name, _to_jsonc_name(name)))
|
|
|
|
|
|
def __delattr__(self, name):
|
|
if name.startswith('_'):
|
|
object.__delattr__(self, name)
|
|
else:
|
|
try:
|
|
del object.__getattribute__(self, '_dict')[_to_jsonc_name(name)]
|
|
except KeyError:
|
|
raise AttributeError(
|
|
'No member for %s (or [\'%s\'])' % (name, _to_jsonc_name(name)))
|
|
|
|
# For container methods pass-through to the underlying dict.
|
|
def __getitem__(self, key):
|
|
return self._dict[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
self._dict[key] = value
|
|
|
|
def __delitem__(self, key):
|
|
del self._dict[key]
|