diff -Nru python-twitter-0.5/CHANGES python-twitter-0.6/CHANGES --- python-twitter-0.5/CHANGES 2007-09-14 03:40:49.000000000 +0100 +++ python-twitter-0.6/CHANGES 2009-06-12 20:31:14.000000000 +0100 @@ -1,3 +1,48 @@ +2009-06-13 + + Releasing 0.6 to help people avoid the Twitpocalypse. + +2009-05-03 + + Support hashlib in addition to the older md5 library. + +2009-03-11 + + Added page parameter to GetReplies, GetFriends, GetFollowers, and GetDirectMessages + +2009-03-03 + + Added count parameter to GetFriendsTimeline + +2009-03-01 + Add PostUpdates, which automatically splits long text into multiple updates. + +2009-02-25 + + Add in_reply_to_status_id to api.PostUpdate + +2009-02-21 + + Wrap any error responses in a TwitterError + Add since_id to GetFriendsTimeline and GetUserTimeline + +2009-02-20 + + Added since and since_id to Api.GetReplies + +2008-07-10 + + Added new properties to User and Status classes. + Removed spurious self-import of the twitter module + Added a NOTICE file + Require simplejson 2.x or later + Added get/create/destroy favorite flags for status messages. + Bug fix for non-tty devices. + +2007-09-13 + + Unset the executable bit on README. + 2007-09-13 Released version 0.5. diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/debian/changelog /tmp/7opw10sJTP/python-twitter-0.6/debian/changelog --- python-twitter-0.5/debian/changelog 2009-06-19 09:12:10.000000000 +0100 +++ python-twitter-0.6/debian/changelog 2009-06-19 09:12:11.000000000 +0100 @@ -1,3 +1,9 @@ +python-twitter (0.6-0ubuntu1~ppa1) jaunty; urgency=low + + * New upstream release + + -- Fumihito YOSHIDA Fri, 19 Jun 2009 16:09:17 +0900 + python-twitter (0.5-2) unstable; urgency=low * Fixed lintian error 'copyright-file-contains-full-apache-2-license' diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/doc/twitter.html /tmp/7opw10sJTP/python-twitter-0.6/doc/twitter.html --- python-twitter-0.5/doc/twitter.html 2007-07-15 07:10:12.000000000 +0100 +++ python-twitter-0.6/doc/twitter.html 2008-07-10 22:43:29.000000000 +0100 @@ -6,9 +6,9 @@  
twitter (version 0.6-devel)
 
- 
twitter (version 0.4)
index
/home/dewitt/svn/python-twitter/twitter.py
+>index
/Users/dewitt/svn/python-twitter/twitter.py

A library that provides a python interface to the Twitter API

@@ -17,16 +17,19 @@ Modules -
       
md5
-os
+
base64
+calendar
+md5
+
os
simplejson
+sys
tempfile
time
twitter
urllib
urllib2
urlparse
-

+

 
@@ -301,6 +304,17 @@   username: The twitter username.
  password: The twitter password. +
SetSource(self, source)
Suggest the "from source" value to be displayed on the Twitter web site.

+The value of the 'source' parameter must be first recognized by
+the Twitter server.  New source values are authorized on a case by
+case basis by the Twitter development team.

+Args:
+  source:
+    The source name as a string.  Will be sent to the server as
+    the 'source' parameter.
+
SetUrllib(self, urllib)
Override the default urllib implementation.
 
Args:
@@ -311,25 +325,35 @@ Args:
  user_agent: a string that should be send to the server as the User-agent
-
__init__(self, username=None, password=None, encoding='utf-8')
Instantiate a new twitter.Api object.
+
SetXTwitterHeaders(self, client, url, version)
Set the X-Twitter HTTP headers that will be sent to the server.

+Args:
+  client:
+     The client name as a string.  Will be sent to the server as
+     the 'X-Twitter-Client' header.
+  url:
+     The URL of the meta.xml as a string.  Will be sent to the server
+     as the 'X-Twitter-Client-URL' header.
+  version:
+     The client version as a string.  Will be sent to the server
+     as the 'X-Twitter-Client-Version' header.
+ +
__init__(self, username=None, password=None, input_encoding=None, request_headers=None)
Instantiate a new twitter.Api object.
 
Args:
  username: The username of the twitter account.  [optional]
  password: The password for the twitter account. [optional]
-  encoding: The string encoding being used. [optional, default 'utf-8']
+  input_encoding: The encoding used to encode input strings. [optional]
+  request_header: A dictionary of additional HTTP request headers. [optional]

-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
-
Data and other attributes defined here:
DEFAULT_CACHE_TIMEOUT = 60
+
__dict__ = <dictproxy object at 0x4cf5d0>
dictionary for instance variables (if defined)
+ +
__weakref__ = <attribute '__weakref__' of 'Api' objects>
list of weak references to the object (if defined)
+

@@ -475,37 +499,112 @@   A twitter.DirectMessage instance
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
+Properties defined here:
created_at
The time this direct message was posted.
+
get = GetCreatedAt(self)
Get the time this direct message was posted.

+Returns:
+  The time this direct message was posted
+
+
set = SetCreatedAt(self, created_at)
Set the time this direct message was posted.

+Args:
+  created_at: The time this direct message was created
+
created_at_in_seconds
The time this direct message was posted, in seconds since the epoch
+
get = GetCreatedAtInSeconds(self)
Get the time this direct message was posted, in seconds since the epoch.

+Returns:
+  The time this direct message was posted, in seconds since the epoch.
+
id
The unique id of this direct message.
+
get = GetId(self)
Get the unique id of this direct message.

+Returns:
+  The unique id of this direct message
+
+
set = SetId(self, id)
Set the unique id of this direct message.

+Args:
+  id: The unique id of this direct message
+
recipient_id
The unique recipient id of this direct message.
+
get = GetRecipientId(self)
Get the unique recipient id of this direct message.

+Returns:
+  The unique recipient id of this direct message
+
+
set = SetRecipientId(self, recipient_id)
Set the unique recipient id of this direct message.

+Args:
+  recipient id: The unique recipient id of this direct message
+
recipient_screen_name
The unique recipient screen name of this direct message.
+
get = GetRecipientScreenName(self)
Get the unique recipient screen name of this direct message.

+Returns:
+  The unique recipient screen name of this direct message
+
+
set = SetRecipientScreenName(self, recipient_screen_name)
Set the unique recipient screen name of this direct message.

+Args:
+  recipient_screen_name: The unique recipient screen name of this direct message
+
sender_id
The unique sender id of this direct message.
+
get = GetSenderId(self)
Get the unique sender id of this direct message.

+Returns:
+  The unique sender id of this direct message
+
+
set = SetSenderId(self, sender_id)
Set the unique sender id of this direct message.

+Args:
+  sender id: The unique sender id of this direct message
+
sender_screen_name
The unique sender screen name of this direct message.
+
get = GetSenderScreenName(self)
Get the unique sender screen name of this direct message.

+Returns:
+  The unique sender screen name of this direct message
+
+
set = SetSenderScreenName(self, sender_screen_name)
Set the unique sender screen name of this direct message.

+Args:
+  sender_screen_name: The unique sender screen name of this direct message
+
text
The text of this direct message
+
get = GetText(self)
Get the text of this direct message.

+Returns:
+  The text of this direct message.
+
+
set = SetText(self, text)
Set the text of this direct message.

+Args:
+  text: The text of this direct message
+
+
+Data and other attributes defined here:
+
__dict__ = <dictproxy object at 0x4cfc90>
dictionary for instance variables (if defined)
+ +
__weakref__ = <attribute '__weakref__' of 'DirectMessage' objects>
list of weak references to the object (if defined)
+

@@ -644,34 +743,101 @@   A twitter.Status instance
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
+Properties defined here:
created_at
The time this status message was posted.
+
get = GetCreatedAt(self)
Get the time this status message was posted.

+Returns:
+  The time this status message was posted
+
+
set = SetCreatedAt(self, created_at)
Set the time this status message was posted.

+Args:
+  created_at: The time this status message was created
+
created_at_in_seconds
The time this status message was posted, in seconds since the epoch
+
get = GetCreatedAtInSeconds(self)
Get the time this status message was posted, in seconds since the epoch.

+Returns:
+  The time this status message was posted, in seconds since the epoch.
+
id
The unique id of this status message.
+
get = GetId(self)
Get the unique id of this status message.

+Returns:
+  The unique id of this status message
+
+
set = SetId(self, id)
Set the unique id of this status message.

+Args:
+  id: The unique id of this status message
+
now
The wallclock time for this status instance.
+
get = GetNow(self)
Get the wallclock time for this status message.

+Used to calculate relative_created_at.  Defaults to the time
+the object was instantiated.

+Returns:
+  Whatever the status instance believes the current time to be,
+  in seconds since the epoch.
+
+
set = SetNow(self, now)
Set the wallclock time for this status message.

+Used to calculate relative_created_at.  Defaults to the time
+the object was instantiated.

+Args:
+  now: The wallclock time for this instance.
+
relative_created_at
Get a human readable string representingthe posting time
+
get = GetRelativeCreatedAt(self)
Get a human redable string representing the posting time

+Returns:
+  A human readable string representing the posting time
+
text
The text of this status message
+
get = GetText(self)
Get the text of this status message.

+Returns:
+  The text of this status message.
+
+
set = SetText(self, text)
Set the text of this status message.

+Args:
+  text: The text of this status message
+
user
A twitter.User reprenting the entity posting this status message
+
get = GetUser(self)
Get a twitter.User reprenting the entity posting this status message.

+Returns:
+  A twitter.User reprenting the entity posting this status message
+
+
set = SetUser(self, user)
Set a twitter.User reprenting the entity posting this status message.

+Args:
+  user: A twitter.User reprenting the entity posting this status message
+
+
+Data and other attributes defined here:
+
__dict__ = <dictproxy object at 0x4cf890>
dictionary for instance variables (if defined)
+ +
__weakref__ = <attribute '__weakref__' of 'Status' objects>
list of weak references to the object (if defined)
+

@@ -824,37 +990,117 @@   A twitter.User instance
-Data descriptors defined here:
-
__dict__
-
dictionary for instance variables (if defined)
-
-
__weakref__
-
list of weak references to the object (if defined)
-
+Properties defined here:
description
The short text description of this user.
+
get = GetDescription(self)
Get the short text description of this user.

+Returns:
+  The short text description of this user
+
+
set = SetDescription(self, description)
Set the short text description of this user.

+Args:
+  description: The short text description of this user
+
id
The unique id of this user.
+
get = GetId(self)
Get the unique id of this user.

+Returns:
+  The unique id of this user
+
+
set = SetId(self, id)
Set the unique id of this user.

+Args:
+  id: The unique id of this user.
+
location
The geographic location of this user.
+
get = GetLocation(self)
Get the geographic location of this user.

+Returns:
+  The geographic location of this user
+
+
set = SetLocation(self, location)
Set the geographic location of this user.

+Args:
+  location: The geographic location of this user
+
name
The real name of this user.
+
get = GetName(self)
Get the real name of this user.

+Returns:
+  The real name of this user
+
+
set = SetName(self, name)
Set the real name of this user.

+Args:
+  name: The real name of this user
+
profile_image_url
The url of the thumbnail of this user.
+
get = GetProfileImageUrl(self)
Get the url of the thumbnail of this user.

+Returns:
+  The url of the thumbnail of this user
+
+
set = SetProfileImageUrl(self, profile_image_url)
Set the url of the thumbnail of this user.

+Args:
+  profile_image_url: The url of the thumbnail of this user
+
screen_name
The short username of this user.
+
get = GetScreenName(self)
Get the short username of this user.

+Returns:
+  The short username of this user
+
+
set = SetScreenName(self, screen_name)
Set the short username of this user.

+Args:
+  screen_name: the short username of this user
+
status
The latest twitter.Status of this user.
+
get = GetStatus(self)
Get the latest twitter.Status of this user.

+Returns:
+  The latest twitter.Status of this user
+
+
set = SetStatus(self, status)
Set the latest twitter.Status of this user.

+Args:
+  status: The latest twitter.Status of this user
+
url
The homepage url of this user.
+
get = GetUrl(self)
Get the homepage url of this user.

+Returns:
+  The homepage url of this user
+
+
set = SetUrl(self, url)
Set the homepage url of this user.

+Args:
+  url: The homepage url of this user
+
+
+Data and other attributes defined here:
+
__dict__ = <dictproxy object at 0x4cffb0>
dictionary for instance variables (if defined)
+ +
__weakref__ = <attribute '__weakref__' of 'User' objects>
list of weak references to the object (if defined)
+

@@ -863,7 +1109,7 @@
        __author__ = 'dewitt@google.com'
-__version__ = '0.4'

+__version__ = '0.6-devel'

 
diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/examples/tweet.py /tmp/7opw10sJTP/python-twitter-0.6/examples/tweet.py --- python-twitter-0.5/examples/tweet.py 2007-07-15 07:10:13.000000000 +0100 +++ python-twitter-0.6/examples/tweet.py 2009-03-31 16:50:08.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2.4 '''Post a message to twitter''' @@ -20,6 +20,7 @@ -h --help : print this help --username : the twitter username [optional] --password : the twitter password [optional] + --encoding : the character set encoding used in input strings, e.g. "utf-8". [optional] Documentation: @@ -108,7 +109,7 @@ status = api.PostUpdate(message) except UnicodeDecodeError: print "Your message could not be encoded. Perhaps it contains non-ASCII characters? " - print "Try explicitly specifying the encoding with the it with the --encoding flag" + print "Try explicitly specifying the encoding with the --encoding flag" sys.exit(2) print "%s just posted: %s" % (status.user.name, status.text) diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/examples/twitter-to-xhtml.py /tmp/7opw10sJTP/python-twitter-0.6/examples/twitter-to-xhtml.py --- python-twitter-0.5/examples/twitter-to-xhtml.py 2007-06-03 21:30:14.000000000 +0100 +++ python-twitter-0.6/examples/twitter-to-xhtml.py 2009-03-31 16:50:33.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python2.4 '''Load the latest update for a Twitter user and leave it in an XHTML fragment''' diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/NOTICE /tmp/7opw10sJTP/python-twitter-0.6/NOTICE --- python-twitter-0.5/NOTICE 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/NOTICE 2009-02-25 16:49:04.000000000 +0000 @@ -0,0 +1,6 @@ +NOTICE + +The simplejson library (http://simplejson.googlecode.com) is used under the terms of the MIT license and is copyright Bob Ippolito. See http://simplejson.googlecode.com/svn/trunk/LICENSE.txt for details. + + +This code is made available under the Apache License and is copyright DeWitt Clinton. diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/PKG-INFO /tmp/7opw10sJTP/python-twitter-0.6/PKG-INFO --- python-twitter-0.5/PKG-INFO 2007-09-14 03:51:08.000000000 +0100 +++ python-twitter-0.6/PKG-INFO 2009-06-12 20:40:59.000000000 +0100 @@ -1,7 +1,7 @@ Metadata-Version: 1.0 Name: python-twitter -Version: 0.5 -Summary: UNKNOWN +Version: 0.6 +Summary: A python wrapper around the Twitter API Home-page: http://code.google.com/p/python-twitter/ Author: DeWitt Clinton Author-email: dewitt@google.com @@ -72,7 +72,7 @@ View the last release API documentation at: - http://static.unto.net/python-twitter/0.5/doc/twitter.html + http://static.unto.net/python-twitter/0.6/doc/twitter.html ==Using== @@ -170,7 +170,8 @@ ==Contributors== Additional thanks to Pierre-Jean Coudert, Omar Kilani, Jodok Batlogg, - and the rest of the python-twitter mailing list. + edleaf,glen.tregoning, Brad Choate, Jim Cortez, Jason Lemoine, Thomas + Dyson, Robert Laquey, and the rest of the python-twitter mailing list. ==License== @@ -190,6 +191,51 @@ limitations under the License. }}} + 2009-06-13 + + Releasing 0.6 to help people avoid the Twitpocalypse. + + 2009-05-03 + + Support hashlib in addition to the older md5 library. + + 2009-03-11 + + Added page parameter to GetReplies, GetFriends, GetFollowers, and GetDirectMessages + + 2009-03-03 + + Added count parameter to GetFriendsTimeline + + 2009-03-01 + Add PostUpdates, which automatically splits long text into multiple updates. + + 2009-02-25 + + Add in_reply_to_status_id to api.PostUpdate + + 2009-02-21 + + Wrap any error responses in a TwitterError + Add since_id to GetFriendsTimeline and GetUserTimeline + + 2009-02-20 + + Added since and since_id to Api.GetReplies + + 2008-07-10 + + Added new properties to User and Status classes. + Removed spurious self-import of the twitter module + Added a NOTICE file + Require simplejson 2.x or later + Added get/create/destroy favorite flags for status messages. + Bug fix for non-tty devices. + + 2007-09-13 + + Unset the executable bit on README. + 2007-09-13 Released version 0.5. diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/python_twitter.egg-info/PKG-INFO /tmp/7opw10sJTP/python-twitter-0.6/python_twitter.egg-info/PKG-INFO --- python-twitter-0.5/python_twitter.egg-info/PKG-INFO 2007-09-14 03:51:08.000000000 +0100 +++ python-twitter-0.6/python_twitter.egg-info/PKG-INFO 2009-06-12 20:40:59.000000000 +0100 @@ -1,7 +1,7 @@ Metadata-Version: 1.0 Name: python-twitter -Version: 0.5 -Summary: UNKNOWN +Version: 0.6 +Summary: A python wrapper around the Twitter API Home-page: http://code.google.com/p/python-twitter/ Author: DeWitt Clinton Author-email: dewitt@google.com @@ -72,7 +72,7 @@ View the last release API documentation at: - http://static.unto.net/python-twitter/0.5/doc/twitter.html + http://static.unto.net/python-twitter/0.6/doc/twitter.html ==Using== @@ -170,7 +170,8 @@ ==Contributors== Additional thanks to Pierre-Jean Coudert, Omar Kilani, Jodok Batlogg, - and the rest of the python-twitter mailing list. + edleaf,glen.tregoning, Brad Choate, Jim Cortez, Jason Lemoine, Thomas + Dyson, Robert Laquey, and the rest of the python-twitter mailing list. ==License== @@ -190,6 +191,51 @@ limitations under the License. }}} + 2009-06-13 + + Releasing 0.6 to help people avoid the Twitpocalypse. + + 2009-05-03 + + Support hashlib in addition to the older md5 library. + + 2009-03-11 + + Added page parameter to GetReplies, GetFriends, GetFollowers, and GetDirectMessages + + 2009-03-03 + + Added count parameter to GetFriendsTimeline + + 2009-03-01 + Add PostUpdates, which automatically splits long text into multiple updates. + + 2009-02-25 + + Add in_reply_to_status_id to api.PostUpdate + + 2009-02-21 + + Wrap any error responses in a TwitterError + Add since_id to GetFriendsTimeline and GetUserTimeline + + 2009-02-20 + + Added since and since_id to Api.GetReplies + + 2008-07-10 + + Added new properties to User and Status classes. + Removed spurious self-import of the twitter module + Added a NOTICE file + Require simplejson 2.x or later + Added get/create/destroy favorite flags for status messages. + Bug fix for non-tty devices. + + 2007-09-13 + + Unset the executable bit on README. + 2007-09-13 Released version 0.5. diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/python_twitter.egg-info/SOURCES.txt /tmp/7opw10sJTP/python-twitter-0.6/python_twitter.egg-info/SOURCES.txt --- python-twitter-0.5/python_twitter.egg-info/SOURCES.txt 2007-09-14 03:51:08.000000000 +0100 +++ python-twitter-0.6/python_twitter.egg-info/SOURCES.txt 2009-06-19 09:12:11.000000000 +0100 @@ -1,7 +1,10 @@ CHANGES COPYING LICENSE +NOTICE README +python-twitter.spec +setup.cfg setup.py twitter.py twitter_test.py @@ -13,6 +16,12 @@ python_twitter.egg-info/dependency_links.txt python_twitter.egg-info/requires.txt python_twitter.egg-info/top_level.txt +simplejson/__init__.py +simplejson/_speedups.c +simplejson/decoder.py +simplejson/encoder.py +simplejson/scanner.py +simplejson/tool.py testdata/direct_message-destroy.json testdata/direct_messages-new.json testdata/direct_messages.json @@ -23,10 +32,11 @@ testdata/friendship-create.json testdata/friendship-destroy.json testdata/public_timeline.json +testdata/public_timeline_error.json testdata/replies.json testdata/show-89512102.json testdata/show-dewitt.json testdata/status-destroy.json testdata/update.json testdata/user_timeline-kesuke.json -testdata/user_timeline.json +testdata/user_timeline.json \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/python-twitter.spec /tmp/7opw10sJTP/python-twitter-0.6/python-twitter.spec --- python-twitter-0.5/python-twitter.spec 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/python-twitter.spec 2009-06-12 20:31:41.000000000 +0100 @@ -0,0 +1,50 @@ +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +Name: python-twitter +Version: 0.6 +Release: 1%{?dist} +Summary: Python Interface for Twitter API + +Group: Development/Libraries +License: Apache License 2.0 +URL: http://code.google.com/p/python-twitter/ +Source0: http://python-twitter.googlecode.com/files/%{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildArch: noarch +Requires: python >= 2.4, python-simplejson >= 2.0.7 +BuildRequires: python-setuptools + + +%description +This library provides a pure python interface for the Twitter API. + + +%prep +%setup -q + + +%build +%{__python} setup.py build + + +%install +rm -rf $RPM_BUILD_ROOT +chmod a-x README +%{__python} setup.py install --skip-build --root $RPM_BUILD_ROOT + + +%clean +rm -rf $RPM_BUILD_ROOT + + +%files +%defattr(-,root,root,-) +%doc PKG-INFO README CHANGES COPYING LICENSE doc/twitter.html +# For noarch packages: sitelib +%{python_sitelib}/* + + +%changelog +* Sat Mar 22 2008 Steve 'Ashcrow' Milner - 0.5-1 +- Initial package. diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/README /tmp/7opw10sJTP/python-twitter-0.6/README --- python-twitter-0.5/README 2007-09-14 03:40:00.000000000 +0100 +++ python-twitter-0.6/README 2009-03-24 20:02:43.000000000 +0000 @@ -64,7 +64,7 @@ View the last release API documentation at: - http://static.unto.net/python-twitter/0.5/doc/twitter.html + http://static.unto.net/python-twitter/0.6/doc/twitter.html ==Using== @@ -162,7 +162,8 @@ ==Contributors== Additional thanks to Pierre-Jean Coudert, Omar Kilani, Jodok Batlogg, -and the rest of the python-twitter mailing list. +edleaf,glen.tregoning, Brad Choate, Jim Cortez, Jason Lemoine, Thomas +Dyson, Robert Laquey, and the rest of the python-twitter mailing list. ==License== diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/setup.py /tmp/7opw10sJTP/python-twitter-0.6/setup.py --- python-twitter-0.5/setup.py 2007-09-14 03:49:36.000000000 +0100 +++ python-twitter-0.6/setup.py 2009-06-12 20:40:34.000000000 +0100 @@ -1,11 +1,23 @@ -#!/usr/bin/python +#!/usr/bin/python2.4 # # Copyright 2007 Google Inc. All Rights Reserved. +# +# 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. '''The setup and build script for the python-twitter library.''' __author__ = 'dewitt@google.com' -__version__ = '0.5' +__version__ = '0.6' # The base package metadata to be used by both distutils and setuptools @@ -15,7 +27,7 @@ py_modules = ['twitter'], author='DeWitt Clinton', author_email='dewitt@google.com', - long_description='A python wrapper around the Twitter API', + description='A python wrapper around the Twitter API', license='Apache License 2.0', url='http://code.google.com/p/python-twitter/', keywords='twitter api', diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/decoder.py /tmp/7opw10sJTP/python-twitter-0.6/simplejson/decoder.py --- python-twitter-0.5/simplejson/decoder.py 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/decoder.py 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,348 @@ +"""Implementation of JSONDecoder +""" +import re +import sys +import struct + +from simplejson.scanner import make_scanner +try: + from simplejson._speedups import scanstring as c_scanstring +except ImportError: + c_scanstring = None + +__all__ = ['JSONDecoder'] + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +def _floatconstants(): + _BYTES = '7FF80000000000007FF0000000000000'.decode('hex') + if sys.byteorder != 'big': + _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1] + nan, inf = struct.unpack('dd', _BYTES) + return nan, inf, -inf + +NaN, PosInf, NegInf = _floatconstants() + + +def linecol(doc, pos): + lineno = doc.count('\n', 0, pos) + 1 + if lineno == 1: + colno = pos + else: + colno = pos - doc.rindex('\n', 0, pos) + return lineno, colno + + +def errmsg(msg, doc, pos, end=None): + # Note that this function is called from _speedups + lineno, colno = linecol(doc, pos) + if end is None: + return '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos) + endlineno, endcolno = linecol(doc, end) + return '%s: line %d column %d - line %d column %d (char %d - %d)' % ( + msg, lineno, colno, endlineno, endcolno, pos, end) + + +_CONSTANTS = { + '-Infinity': NegInf, + 'Infinity': PosInf, + 'NaN': NaN, +} + +STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) +BACKSLASH = { + '"': u'"', '\\': u'\\', '/': u'/', + 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t', +} + +DEFAULT_ENCODING = "utf-8" + +def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match): + """Scan the string s for a JSON string. End is the index of the + character in s after the quote that started the JSON string. + Unescapes all valid JSON string escape sequences and raises ValueError + on attempt to decode an invalid string. If strict is False then literal + control characters are allowed in the string. + + Returns a tuple of the decoded string and the index of the character in s + after the end quote.""" + if encoding is None: + encoding = DEFAULT_ENCODING + chunks = [] + _append = chunks.append + begin = end - 1 + while 1: + chunk = _m(s, end) + if chunk is None: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + end = chunk.end() + content, terminator = chunk.groups() + # Content is contains zero or more unescaped string characters + if content: + if not isinstance(content, unicode): + content = unicode(content, encoding) + _append(content) + # Terminator is the end of string, a literal control character, + # or a backslash denoting that an escape sequence follows + if terminator == '"': + break + elif terminator != '\\': + if strict: + msg = "Invalid control character %r at" % (terminator,) + raise ValueError(msg, s, end) + else: + _append(terminator) + continue + try: + esc = s[end] + except IndexError: + raise ValueError( + errmsg("Unterminated string starting at", s, begin)) + # If not a unicode escape sequence, must be in the lookup table + if esc != 'u': + try: + char = _b[esc] + except KeyError: + raise ValueError( + errmsg("Invalid \\escape: %r" % (esc,), s, end)) + end += 1 + else: + # Unicode escape sequence + esc = s[end + 1:end + 5] + next_end = end + 5 + if len(esc) != 4: + msg = "Invalid \\uXXXX escape" + raise ValueError(errmsg(msg, s, end)) + uni = int(esc, 16) + # Check for surrogate pair on UCS-4 systems + if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535: + msg = "Invalid \\uXXXX\\uXXXX surrogate pair" + if not s[end + 5:end + 7] == '\\u': + raise ValueError(errmsg(msg, s, end)) + esc2 = s[end + 7:end + 11] + if len(esc2) != 4: + raise ValueError(errmsg(msg, s, end)) + uni2 = int(esc2, 16) + uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00)) + next_end += 6 + char = unichr(uni) + end = next_end + # Append the unescaped character + _append(char) + return u''.join(chunks), end + + +# Use speedup if available +scanstring = c_scanstring or py_scanstring + +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + +def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + pairs = {} + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + return pairs, end + 1 + elif nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end)) + end += 1 + while True: + key, end = scanstring(s, end, encoding, strict) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting : delimiter", s, end)) + + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + pairs[key] = value + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise ValueError(errmsg("Expecting property name", s, end - 1)) + + if object_hook is not None: + pairs = object_hook(pairs) + return pairs, end + +def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + values = [] + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return values, end + 1 + _append = values.append + while True: + try: + value, end = scan_once(s, end) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting , delimiter", s, end)) + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return values, end + +class JSONDecoder(object): + """Simple JSON decoder + + Performs the following translations in decoding by default: + + +---------------+-------------------+ + | JSON | Python | + +===============+===================+ + | object | dict | + +---------------+-------------------+ + | array | list | + +---------------+-------------------+ + | string | unicode | + +---------------+-------------------+ + | number (int) | int, long | + +---------------+-------------------+ + | number (real) | float | + +---------------+-------------------+ + | true | True | + +---------------+-------------------+ + | false | False | + +---------------+-------------------+ + | null | None | + +---------------+-------------------+ + + It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as + their corresponding ``float`` values, which is outside the JSON spec. + + """ + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True): + """``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + """ + self.encoding = encoding + self.object_hook = object_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or _CONSTANTS.__getitem__ + self.strict = strict + self.parse_object = JSONObject + self.parse_array = JSONArray + self.parse_string = scanstring + self.scan_once = make_scanner(self) + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, idx=0): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning + with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + obj, end = self.scan_once(s, idx) + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/encoder.py /tmp/7opw10sJTP/python-twitter-0.6/simplejson/encoder.py --- python-twitter-0.5/simplejson/encoder.py 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/encoder.py 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,436 @@ +"""Implementation of JSONEncoder +""" +import re + +try: + from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii +except ImportError: + c_encode_basestring_ascii = None +try: + from simplejson._speedups import make_encoder as c_make_encoder +except ImportError: + c_make_encoder = None + +ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]') +ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])') +HAS_UTF8 = re.compile(r'[\x80-\xff]') +ESCAPE_DCT = { + '\\': '\\\\', + '"': '\\"', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', +} +for i in range(0x20): + ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) + +# Assume this produces an infinity on all machines (probably not guaranteed) +INFINITY = float('1e66666') +FLOAT_REPR = repr + +def encode_basestring(s): + """Return a JSON representation of a Python string + + """ + def replace(match): + return ESCAPE_DCT[match.group(0)] + return '"' + ESCAPE.sub(replace, s) + '"' + + +def py_encode_basestring_ascii(s): + """Return an ASCII-only JSON representation of a Python string + + """ + if isinstance(s, str) and HAS_UTF8.search(s) is not None: + s = s.decode('utf-8') + def replace(match): + s = match.group(0) + try: + return ESCAPE_DCT[s] + except KeyError: + n = ord(s) + if n < 0x10000: + return '\\u%04x' % (n,) + else: + # surrogate pair + n -= 0x10000 + s1 = 0xd800 | ((n >> 10) & 0x3ff) + s2 = 0xdc00 | (n & 0x3ff) + return '\\u%04x\\u%04x' % (s1, s2) + return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"' + + +encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii + +class JSONEncoder(object): + """Extensible JSON encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is False, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If ensure_ascii is True, the output is guaranteed to be str + objects with all incoming unicode characters escaped. If + ensure_ascii is false, the output will be unicode object. + + If check_circular is True, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is True, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is True, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + def default(self, o): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + return JSONEncoder.default(self, o) + + """ + raise TypeError("%r is not JSON serializable" % (o,)) + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + chunks = self.iterencode(o, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + return ''.join(chunks) + + def iterencode(self, o, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + if self.ensure_ascii: + _encoder = encode_basestring_ascii + else: + _encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + + def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): + # Check for specials. Note that this type of test is processor- and/or + # platform-specific, so do tests which don't depend on the internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError("Out of range float values are not JSON compliant: %r" + % (o,)) + + return text + + + if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys: + _iterencode = c_make_encoder( + markers, self.default, _encoder, self.indent, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, self.allow_nan) + else: + _iterencode = _make_iterencode( + markers, self.default, _encoder, self.indent, floatstr, + self.key_separator, self.item_separator, self.sort_keys, + self.skipkeys, _one_shot) + return _iterencode(o, 0) + +def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, + ## HACK: hand-optimized bytecode; turn globals into locals + False=False, + True=True, + ValueError=ValueError, + basestring=basestring, + dict=dict, + float=float, + id=id, + int=int, + isinstance=isinstance, + list=list, + long=long, + str=str, + tuple=tuple, + ): + + def _iterencode_list(lst, _current_indent_level): + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + buf = '[' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + else: + newline_indent = None + separator = _item_separator + first = True + for value in lst: + if first: + first = False + else: + buf = separator + if isinstance(value, basestring): + yield buf + _encoder(value) + elif value is None: + yield buf + 'null' + elif value is True: + yield buf + 'true' + elif value is False: + yield buf + 'false' + elif isinstance(value, (int, long)): + yield buf + str(value) + elif isinstance(value, float): + yield buf + _floatstr(value) + else: + yield buf + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(dct, _current_indent_level): + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + if _indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + item_separator = _item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = _item_separator + first = True + if _sort_keys: + items = dct.items() + items.sort(key=lambda kv: kv[0]) + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = _floatstr(key) + elif isinstance(key, (int, long)): + key = str(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif _skipkeys: + continue + else: + raise TypeError("key %r is not a string" % (key,)) + if first: + first = False + else: + yield item_separator + yield _encoder(key) + yield _key_separator + if isinstance(value, basestring): + yield _encoder(value) + elif value is None: + yield 'null' + elif value is True: + yield 'true' + elif value is False: + yield 'false' + elif isinstance(value, (int, long)): + yield str(value) + elif isinstance(value, float): + yield _floatstr(value) + else: + if isinstance(value, (list, tuple)): + chunks = _iterencode_list(value, _current_indent_level) + elif isinstance(value, dict): + chunks = _iterencode_dict(value, _current_indent_level) + else: + chunks = _iterencode(value, _current_indent_level) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (_indent * _current_indent_level)) + yield '}' + if markers is not None: + del markers[markerid] + + def _iterencode(o, _current_indent_level): + if isinstance(o, basestring): + yield _encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield _floatstr(o) + elif isinstance(o, (list, tuple)): + for chunk in _iterencode_list(o, _current_indent_level): + yield chunk + elif isinstance(o, dict): + for chunk in _iterencode_dict(o, _current_indent_level): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = _default(o) + for chunk in _iterencode(o, _current_indent_level): + yield chunk + if markers is not None: + del markers[markerid] + + return _iterencode diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/__init__.py /tmp/7opw10sJTP/python-twitter-0.6/simplejson/__init__.py --- python-twitter-0.5/simplejson/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/__init__.py 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,316 @@ +r"""JSON (JavaScript Object Notation) is a subset of +JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data +interchange format. + +:mod:`simplejson` exposes an API familiar to users of the standard library +:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained +version of the :mod:`json` library contained in Python 2.6, but maintains +compatibility with Python 2.4 and Python 2.5 and (currently) has +significant performance advantages, even without using the optional C +extension for speedups. + +Encoding basic Python object hierarchies:: + + >>> import simplejson as json + >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}]) + '["foo", {"bar": ["baz", null, 1.0, 2]}]' + >>> print json.dumps("\"foo\bar") + "\"foo\bar" + >>> print json.dumps(u'\u1234') + "\u1234" + >>> print json.dumps('\\') + "\\" + >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True) + {"a": 0, "b": 0, "c": 0} + >>> from StringIO import StringIO + >>> io = StringIO() + >>> json.dump(['streaming API'], io) + >>> io.getvalue() + '["streaming API"]' + +Compact encoding:: + + >>> import simplejson as json + >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':')) + '[1,2,3,{"4":5,"6":7}]' + +Pretty printing:: + + >>> import simplejson as json + >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) + >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + { + "4": 5, + "6": 7 + } + +Decoding JSON:: + + >>> import simplejson as json + >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}] + >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj + True + >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar' + True + >>> from StringIO import StringIO + >>> io = StringIO('["streaming API"]') + >>> json.load(io)[0] == 'streaming API' + True + +Specializing JSON object decoding:: + + >>> import simplejson as json + >>> def as_complex(dct): + ... if '__complex__' in dct: + ... return complex(dct['real'], dct['imag']) + ... return dct + ... + >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}', + ... object_hook=as_complex) + (1+2j) + >>> import decimal + >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1') + True + +Specializing JSON object encoding:: + + >>> import simplejson as json + >>> def encode_complex(obj): + ... if isinstance(obj, complex): + ... return [obj.real, obj.imag] + ... raise TypeError("%r is not JSON serializable" % (o,)) + ... + >>> json.dumps(2 + 1j, default=encode_complex) + '[2.0, 1.0]' + >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j) + '[2.0, 1.0]' + >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j)) + '[2.0, 1.0]' + + +Using simplejson.tool from the shell to validate and pretty-print:: + + $ echo '{"json":"obj"}' | python -msimplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -msimplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +__version__ = '2.0.7' +__all__ = [ + 'dump', 'dumps', 'load', 'loads', + 'JSONDecoder', 'JSONEncoder', +] + +from decoder import JSONDecoder +from encoder import JSONEncoder + +_default_encoder = JSONEncoder( + skipkeys=False, + ensure_ascii=True, + check_circular=True, + allow_nan=True, + indent=None, + separators=None, + encoding='utf-8', + default=None, +) + +def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a + ``.write()``-supporting file-like object). + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the some chunks written to ``fp`` + may be ``unicode`` instances, subject to normal Python ``str`` to + ``unicode`` coercion rules. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter()``) this is likely + to cause an error. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) + in strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and object + members will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. ``None`` is the most compact representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (skipkeys is False and ensure_ascii is True and + check_circular is True and allow_nan is True and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + iterable = _default_encoder.iterencode(obj) + else: + if cls is None: + cls = JSONEncoder + iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, + default=default, **kw).iterencode(obj) + # could accelerate with writelines in some versions of Python, at + # a debuggability cost + for chunk in iterable: + fp.write(chunk) + + +def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, + allow_nan=True, cls=None, indent=None, separators=None, + encoding='utf-8', default=None, **kw): + """Serialize ``obj`` to a JSON formatted ``str``. + + If ``skipkeys`` is ``True`` then ``dict`` keys that are not basic types + (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) + will be skipped instead of raising a ``TypeError``. + + If ``ensure_ascii`` is ``False``, then the return value will be a + ``unicode`` instance subject to normal Python ``str`` to ``unicode`` + coercion rules instead of being escaped to an ASCII ``str``. + + If ``check_circular`` is ``False``, then the circular reference check + for container types will be skipped and a circular reference will + result in an ``OverflowError`` (or worse). + + If ``allow_nan`` is ``False``, then it will be a ``ValueError`` to + serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in + strict compliance of the JSON specification, instead of using the + JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). + + If ``indent`` is a non-negative integer, then JSON array elements and + object members will be pretty-printed with that indent level. An indent + level of 0 will only insert newlines. ``None`` is the most compact + representation. + + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` separators. + ``(',', ':')`` is the most compact JSON representation. + + ``encoding`` is the character encoding for str instances, default is UTF-8. + + ``default(obj)`` is a function that should return a serializable version + of obj or raise TypeError. The default simply raises TypeError. + + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the + ``.default()`` method to serialize additional types), specify it with + the ``cls`` kwarg. + + """ + # cached encoder + if (skipkeys is False and ensure_ascii is True and + check_circular is True and allow_nan is True and + cls is None and indent is None and separators is None and + encoding == 'utf-8' and default is None and not kw): + return _default_encoder.encode(obj) + if cls is None: + cls = JSONEncoder + return cls( + skipkeys=skipkeys, ensure_ascii=ensure_ascii, + check_circular=check_circular, allow_nan=allow_nan, indent=indent, + separators=separators, encoding=encoding, default=default, + **kw).encode(obj) + + +_default_decoder = JSONDecoder(encoding=None, object_hook=None) + + +def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing + a JSON document) to a Python object. + + If the contents of ``fp`` is encoded with an ASCII based encoding other + than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must + be specified. Encodings that are not ASCII based (such as UCS-2) are + not allowed, and should be wrapped with + ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode`` + object and passed to ``loads()`` + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + return loads(fp.read(), + encoding=encoding, cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, **kw) + + +def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, **kw): + """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON + document) to a Python object. + + If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding + other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name + must be specified. Encodings that are not ASCII based (such as UCS-2) + are not allowed and should be decoded to ``unicode`` first. + + ``object_hook`` is an optional function that will be called with the + result of any object literal decode (a ``dict``). The return value of + ``object_hook`` will be used instead of the ``dict``. This feature + can be used to implement custom decoders (e.g. JSON-RPC class hinting). + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN, null, true, false. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + To use a custom ``JSONDecoder`` subclass, specify it with the ``cls`` + kwarg. + + """ + if (cls is None and encoding is None and object_hook is None and + parse_int is None and parse_float is None and + parse_constant is None and not kw): + return _default_decoder.decode(s) + if cls is None: + cls = JSONDecoder + if object_hook is not None: + kw['object_hook'] = object_hook + if parse_float is not None: + kw['parse_float'] = parse_float + if parse_int is not None: + kw['parse_int'] = parse_int + if parse_constant is not None: + kw['parse_constant'] = parse_constant + return cls(encoding=encoding, **kw).decode(s) diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/scanner.py /tmp/7opw10sJTP/python-twitter-0.6/simplejson/scanner.py --- python-twitter-0.5/simplejson/scanner.py 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/scanner.py 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,65 @@ +"""JSON token scanner +""" +import re +try: + from simplejson._speedups import make_scanner as c_make_scanner +except ImportError: + c_make_scanner = None + +__all__ = ['make_scanner'] + +NUMBER_RE = re.compile( + r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', + (re.VERBOSE | re.MULTILINE | re.DOTALL)) + +def py_make_scanner(context): + parse_object = context.parse_object + parse_array = context.parse_array + parse_string = context.parse_string + match_number = NUMBER_RE.match + encoding = context.encoding + strict = context.strict + parse_float = context.parse_float + parse_int = context.parse_int + parse_constant = context.parse_constant + object_hook = context.object_hook + + def _scan_once(string, idx): + try: + nextchar = string[idx] + except IndexError: + raise StopIteration + + if nextchar == '"': + return parse_string(string, idx + 1, encoding, strict) + elif nextchar == '{': + return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook) + elif nextchar == '[': + return parse_array((string, idx + 1), _scan_once) + elif nextchar == 'n' and string[idx:idx + 4] == 'null': + return None, idx + 4 + elif nextchar == 't' and string[idx:idx + 4] == 'true': + return True, idx + 4 + elif nextchar == 'f' and string[idx:idx + 5] == 'false': + return False, idx + 5 + + m = match_number(string, idx) + if m is not None: + integer, frac, exp = m.groups() + if frac or exp: + res = parse_float(integer + (frac or '') + (exp or '')) + else: + res = parse_int(integer) + return res, m.end() + elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': + return parse_constant('NaN'), idx + 3 + elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': + return parse_constant('Infinity'), idx + 8 + elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': + return parse_constant('-Infinity'), idx + 9 + else: + raise StopIteration + + return _scan_once + +make_scanner = c_make_scanner or py_make_scanner diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/_speedups.c /tmp/7opw10sJTP/python-twitter-0.6/simplejson/_speedups.c --- python-twitter-0.5/simplejson/_speedups.c 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/_speedups.c 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,2265 @@ +#include "Python.h" +#include "structmember.h" +#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE) +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif +#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#define PyInt_FromSsize_t PyInt_FromLong +#define PyInt_AsSsize_t PyInt_AsLong +#endif +#ifndef Py_IS_FINITE +#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X)) +#endif + +#ifdef __GNUC__ +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#define DEFAULT_ENCODING "utf-8" + +#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType) +#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType) +#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType) +#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType) + +static PyTypeObject PyScannerType; +static PyTypeObject PyEncoderType; + +typedef struct _PyScannerObject { + PyObject_HEAD + PyObject *encoding; + PyObject *strict; + PyObject *object_hook; + PyObject *parse_float; + PyObject *parse_int; + PyObject *parse_constant; +} PyScannerObject; + +static PyMemberDef scanner_members[] = { + {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"}, + {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"}, + {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"}, + {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"}, + {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"}, + {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"}, + {NULL} +}; + +typedef struct _PyEncoderObject { + PyObject_HEAD + PyObject *markers; + PyObject *defaultfn; + PyObject *encoder; + PyObject *indent; + PyObject *key_separator; + PyObject *item_separator; + PyObject *sort_keys; + PyObject *skipkeys; + int fast_encode; + int allow_nan; +} PyEncoderObject; + +static PyMemberDef encoder_members[] = { + {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"}, + {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"}, + {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"}, + {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"}, + {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"}, + {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"}, + {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"}, + {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"}, + {NULL} +}; + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars); +static PyObject * +ascii_escape_unicode(PyObject *pystr); +static PyObject * +ascii_escape_str(PyObject *pystr); +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr); +void init_speedups(void); +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr); +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx); +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +scanner_dealloc(PyObject *self); +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds); +static void +encoder_dealloc(PyObject *self); +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level); +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level); +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level); +static PyObject * +_encoded_const(PyObject *const); +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end); +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj); +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr); +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr); +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj); + +#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"') +#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r')) + +#define MIN_EXPANSION 6 +#ifdef Py_UNICODE_WIDE +#define MAX_EXPANSION (2 * MIN_EXPANSION) +#else +#define MAX_EXPANSION MIN_EXPANSION +#endif + +static int +_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr) +{ + /* PyObject to Py_ssize_t converter */ + *size_ptr = PyInt_AsSsize_t(o); + if (*size_ptr == -1 && PyErr_Occurred()); + return 1; + return 0; +} + +static PyObject * +_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr) +{ + /* Py_ssize_t to PyObject converter */ + return PyInt_FromSsize_t(*size_ptr); +} + +static Py_ssize_t +ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars) +{ + /* Escape unicode code point c to ASCII escape sequences + in char *output. output must have at least 12 bytes unused to + accommodate an escaped surrogate pair "\uXXXX\uXXXX" */ + output[chars++] = '\\'; + switch (c) { + case '\\': output[chars++] = (char)c; break; + case '"': output[chars++] = (char)c; break; + case '\b': output[chars++] = 'b'; break; + case '\f': output[chars++] = 'f'; break; + case '\n': output[chars++] = 'n'; break; + case '\r': output[chars++] = 'r'; break; + case '\t': output[chars++] = 't'; break; + default: +#ifdef Py_UNICODE_WIDE + if (c >= 0x10000) { + /* UTF-16 surrogate pair */ + Py_UNICODE v = c - 0x10000; + c = 0xd800 | ((v >> 10) & 0x3ff); + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + c = 0xdc00 | (v & 0x3ff); + output[chars++] = '\\'; + } +#endif + output[chars++] = 'u'; + output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf]; + output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf]; + output[chars++] = "0123456789abcdef"[(c ) & 0xf]; + } + return chars; +} + +static PyObject * +ascii_escape_unicode(PyObject *pystr) +{ + /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t max_output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + Py_UNICODE *input_unicode; + + input_chars = PyUnicode_GET_SIZE(pystr); + input_unicode = PyUnicode_AS_UNICODE(pystr); + + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + max_output_size = 2 + (input_chars * MAX_EXPANSION); + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + chars = 0; + output[chars++] = '"'; + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = input_unicode[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + if (output_size - chars < (1 + MAX_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + Py_ssize_t new_output_size = output_size * 2; + /* This is an upper bound */ + if (new_output_size > max_output_size) { + new_output_size = max_output_size; + } + /* Make sure that the output size changed before resizing */ + if (new_output_size != output_size) { + output_size = new_output_size; + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static PyObject * +ascii_escape_str(PyObject *pystr) +{ + /* Take a PyString pystr and return a new ASCII-only escaped PyString */ + Py_ssize_t i; + Py_ssize_t input_chars; + Py_ssize_t output_size; + Py_ssize_t chars; + PyObject *rval; + char *output; + char *input_str; + + input_chars = PyString_GET_SIZE(pystr); + input_str = PyString_AS_STRING(pystr); + + /* Fast path for a string that's already ASCII */ + for (i = 0; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (!S_CHAR(c)) { + /* If we have to escape something, scan the string for unicode */ + Py_ssize_t j; + for (j = i; j < input_chars; j++) { + c = (Py_UNICODE)(unsigned char)input_str[j]; + if (c > 0x7f) { + /* We hit a non-ASCII character, bail to unicode mode */ + PyObject *uni; + uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict"); + if (uni == NULL) { + return NULL; + } + rval = ascii_escape_unicode(uni); + Py_DECREF(uni); + return rval; + } + } + break; + } + } + + if (i == input_chars) { + /* Input is already ASCII */ + output_size = 2 + input_chars; + } + else { + /* One char input can be up to 6 chars output, estimate 4 of these */ + output_size = 2 + (MIN_EXPANSION * 4) + input_chars; + } + rval = PyString_FromStringAndSize(NULL, output_size); + if (rval == NULL) { + return NULL; + } + output = PyString_AS_STRING(rval); + output[0] = '"'; + + /* We know that everything up to i is ASCII already */ + chars = i + 1; + memcpy(&output[1], input_str, i); + + for (; i < input_chars; i++) { + Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; + if (S_CHAR(c)) { + output[chars++] = (char)c; + } + else { + chars = ascii_escape_char(c, output, chars); + } + /* An ASCII char can't possibly expand to a surrogate! */ + if (output_size - chars < (1 + MIN_EXPANSION)) { + /* There's more than four, so let's resize by a lot */ + output_size *= 2; + if (output_size > 2 + (input_chars * MIN_EXPANSION)) { + output_size = 2 + (input_chars * MIN_EXPANSION); + } + if (_PyString_Resize(&rval, output_size) == -1) { + return NULL; + } + output = PyString_AS_STRING(rval); + } + } + output[chars++] = '"'; + if (_PyString_Resize(&rval, chars) == -1) { + return NULL; + } + return rval; +} + +static void +raise_errmsg(char *msg, PyObject *s, Py_ssize_t end) +{ + /* Use the Python function simplejson.decoder.errmsg to raise a nice + looking ValueError exception */ + static PyObject *errmsg_fn = NULL; + PyObject *pymsg; + if (errmsg_fn == NULL) { + PyObject *decoder = PyImport_ImportModule("simplejson.decoder"); + if (decoder == NULL) + return; + errmsg_fn = PyObject_GetAttrString(decoder, "errmsg"); + Py_DECREF(decoder); + if (errmsg_fn == NULL) + return; + } + pymsg = PyObject_CallFunction(errmsg_fn, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end); + if (pymsg) { + PyErr_SetObject(PyExc_ValueError, pymsg); + Py_DECREF(pymsg); + } +} + +static PyObject * +join_list_unicode(PyObject *lst) +{ + /* return u''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyUnicode_FromUnicode(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +join_list_string(PyObject *lst) +{ + /* return ''.join(lst) */ + static PyObject *joinfn = NULL; + if (joinfn == NULL) { + PyObject *ustr = PyString_FromStringAndSize(NULL, 0); + if (ustr == NULL) + return NULL; + + joinfn = PyObject_GetAttrString(ustr, "join"); + Py_DECREF(ustr); + if (joinfn == NULL) + return NULL; + } + return PyObject_CallFunctionObjArgs(joinfn, lst, NULL); +} + +static PyObject * +_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) { + /* return (rval, idx) tuple, stealing reference to rval */ + PyObject *tpl; + PyObject *pyidx; + /* + steal a reference to rval, returns (rval, idx) + */ + if (rval == NULL) { + return NULL; + } + pyidx = PyInt_FromSsize_t(idx); + if (pyidx == NULL) { + Py_DECREF(rval); + return NULL; + } + tpl = PyTuple_New(2); + if (tpl == NULL) { + Py_DECREF(pyidx); + Py_DECREF(rval); + return NULL; + } + PyTuple_SET_ITEM(tpl, 0, rval); + PyTuple_SET_ITEM(tpl, 1, pyidx); + return tpl; +} + +static PyObject * +scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyString pystr. + end is the index of the first character after the quote. + encoding is the encoding of pystr (must be an ASCII superset) + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyString (if ASCII-only) or PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyString_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + int has_unicode = 0; + char *buf = PyString_AS_STRING(pystr); + PyObject *chunks = PyList_New(0); + if (chunks == NULL) { + goto bail; + } + if (end < 0 || len <= end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + PyObject *chunk = NULL; + for (next = end; next < len; next++) { + c = (unsigned char)buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + else if (c > 0x7f) { + has_unicode = 1; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + PyObject *strchunk = PyString_FromStringAndSize(&buf[end], next - end); + if (strchunk == NULL) { + goto bail; + } + if (has_unicode) { + chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL); + Py_DECREF(strchunk); + if (chunk == NULL) { + goto bail; + } + } + else { + chunk = strchunk; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + if (c > 0x7f) { + has_unicode = 1; + } + if (has_unicode) { + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + } + else { + char c_char = Py_CHARMASK(c); + chunk = PyString_FromStringAndSize(&c_char, 1); + if (chunk == NULL) { + goto bail; + } + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + + rval = join_list_string(chunks); + if (rval == NULL) { + goto bail; + } + Py_CLEAR(chunks); + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunks); + return NULL; +} + + +static PyObject * +scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr) +{ + /* Read the JSON string from PyUnicode pystr. + end is the index of the first character after the quote. + encoding is the encoding of pystr (must be an ASCII superset) + if strict is zero then literal control characters are allowed + *next_end_ptr is a return-by-reference index of the character + after the end quote + + Return value is a new PyUnicode + */ + PyObject *rval; + Py_ssize_t len = PyUnicode_GET_SIZE(pystr); + Py_ssize_t begin = end - 1; + Py_ssize_t next = begin; + const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr); + PyObject *chunks = PyList_New(0); + if (chunks == NULL) { + goto bail; + } + if (end < 0 || len <= end) { + PyErr_SetString(PyExc_ValueError, "end is out of bounds"); + goto bail; + } + while (1) { + /* Find the end of the string or the next escape */ + Py_UNICODE c = 0; + PyObject *chunk = NULL; + for (next = end; next < len; next++) { + c = buf[next]; + if (c == '"' || c == '\\') { + break; + } + else if (strict && c <= 0x1f) { + raise_errmsg("Invalid control character at", pystr, next); + goto bail; + } + } + if (!(c == '"' || c == '\\')) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + /* Pick up this chunk if it's not zero length */ + if (next != end) { + chunk = PyUnicode_FromUnicode(&buf[end], next - end); + if (chunk == NULL) { + goto bail; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + next++; + if (c == '"') { + end = next; + break; + } + if (next == len) { + raise_errmsg("Unterminated string starting at", pystr, begin); + goto bail; + } + c = buf[next]; + if (c != 'u') { + /* Non-unicode backslash escapes */ + end = next + 1; + switch (c) { + case '"': break; + case '\\': break; + case '/': break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: c = 0; + } + if (c == 0) { + raise_errmsg("Invalid \\escape", pystr, end - 2); + goto bail; + } + } + else { + c = 0; + next++; + end = next + 4; + if (end >= len) { + raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1); + goto bail; + } + /* Decode 4 hex digits */ + for (; next < end; next++) { + Py_UNICODE digit = buf[next]; + c <<= 4; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } +#ifdef Py_UNICODE_WIDE + /* Surrogate pair */ + if ((c & 0xfc00) == 0xd800) { + Py_UNICODE c2 = 0; + if (end + 6 >= len) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + if (buf[next++] != '\\' || buf[next++] != 'u') { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + end += 6; + /* Decode 4 hex digits */ + for (; next < end; next++) { + c2 <<= 4; + Py_UNICODE digit = buf[next]; + switch (digit) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c2 |= (digit - '0'); break; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + c2 |= (digit - 'a' + 10); break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + c2 |= (digit - 'A' + 10); break; + default: + raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5); + goto bail; + } + } + if ((c2 & 0xfc00) != 0xdc00) { + raise_errmsg("Unpaired high surrogate", pystr, end - 5); + goto bail; + } + c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00)); + } + else if ((c & 0xfc00) == 0xdc00) { + raise_errmsg("Unpaired low surrogate", pystr, end - 5); + goto bail; + } +#endif + } + chunk = PyUnicode_FromUnicode(&c, 1); + if (chunk == NULL) { + goto bail; + } + if (PyList_Append(chunks, chunk)) { + Py_DECREF(chunk); + goto bail; + } + Py_DECREF(chunk); + } + + rval = join_list_unicode(chunks); + if (rval == NULL) { + goto bail; + } + Py_DECREF(chunks); + *next_end_ptr = end; + return rval; +bail: + *next_end_ptr = -1; + Py_XDECREF(chunks); + return NULL; +} + +PyDoc_STRVAR(pydoc_scanstring, + "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n" + "\n" + "Scan the string s for a JSON string. End is the index of the\n" + "character in s after the quote that started the JSON string.\n" + "Unescapes all valid JSON string escape sequences and raises ValueError\n" + "on attempt to decode an invalid string. If strict is False then literal\n" + "control characters are allowed in the string.\n" + "\n" + "Returns a tuple of the decoded string and the index of the character in s\n" + "after the end quote." +); + +static PyObject * +py_scanstring(PyObject* self UNUSED, PyObject *args) +{ + PyObject *pystr; + PyObject *rval; + Py_ssize_t end; + Py_ssize_t next_end = -1; + char *encoding = NULL; + int strict = 1; + if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) { + return NULL; + } + if (encoding == NULL) { + encoding = DEFAULT_ENCODING; + } + if (PyString_Check(pystr)) { + rval = scanstring_str(pystr, end, encoding, strict, &next_end); + } + else if (PyUnicode_Check(pystr)) { + rval = scanstring_unicode(pystr, end, strict, &next_end); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + return _build_rval_index_tuple(rval, next_end); +} + +PyDoc_STRVAR(pydoc_encode_basestring_ascii, + "encode_basestring_ascii(basestring) -> str\n" + "\n" + "Return an ASCII-only JSON representation of a Python string" +); + +static PyObject * +py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr) +{ + /* Return an ASCII-only JSON representation of a Python string */ + /* METH_O */ + if (PyString_Check(pystr)) { + return ascii_escape_str(pystr); + } + else if (PyUnicode_Check(pystr)) { + return ascii_escape_unicode(pystr); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } +} + +static void +scanner_dealloc(PyObject *self) +{ + /* Deallocate scanner object */ + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + self->ob_type->tp_free(self); +} + +static PyObject * +_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyString pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook can change that) + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *rval = PyDict_New(); + PyObject *key = NULL; + PyObject *val = NULL; + char *encoding = PyString_AS_STRING(s->encoding); + int strict = PyObject_IsTrue(s->strict); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + /* read key */ + if (str[idx] != '"') { + raise_errmsg("Expecting property name", pystr, idx); + goto bail; + } + key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx); + if (key == NULL) + goto bail; + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting : delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON data type */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyDict_SetItem(rval, key, val) == -1) + goto bail; + + Py_CLEAR(key); + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(key); + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON object from PyUnicode pystr. + idx is the index of the first character after the opening curly brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing curly brace. + + Returns a new PyObject (usually a dict, but object_hook can change that) + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyDict_New(); + PyObject *key = NULL; + int strict = PyObject_IsTrue(s->strict); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after { */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the object is non-empty */ + if (idx <= end_idx && str[idx] != '}') { + while (idx <= end_idx) { + /* read key */ + if (str[idx] != '"') { + raise_errmsg("Expecting property name", pystr, idx); + goto bail; + } + key = scanstring_unicode(pystr, idx + 1, strict, &next_idx); + if (key == NULL) + goto bail; + idx = next_idx; + + /* skip whitespace between key and : delimiter, read :, skip whitespace */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + if (idx > end_idx || str[idx] != ':') { + raise_errmsg("Expecting : delimiter", pystr, idx); + goto bail; + } + idx++; + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyDict_SetItem(rval, key, val) == -1) + goto bail; + + Py_CLEAR(key); + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace before } or , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the object is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == '}') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , delimiter */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be '}' */ + if (idx > end_idx || str[idx] != '}') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + + /* if object_hook is not None: rval = object_hook(rval) */ + if (s->object_hook != Py_None) { + val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL); + if (val == NULL) + goto bail; + Py_DECREF(rval); + rval = val; + val = NULL; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(key); + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term and de-tuplefy the (rval, idx) */ + val = scan_once_str(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON array from PyString pystr. + idx is the index of the first character after the opening brace. + *next_idx_ptr is a return-by-reference index to the first character after + the closing brace. + + Returns a new PyList + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + PyObject *val = NULL; + PyObject *rval = PyList_New(0); + Py_ssize_t next_idx; + if (rval == NULL) + return NULL; + + /* skip whitespace after [ */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* only loop if the array is non-empty */ + if (idx <= end_idx && str[idx] != ']') { + while (idx <= end_idx) { + + /* read any JSON term */ + val = scan_once_unicode(s, pystr, idx, &next_idx); + if (val == NULL) + goto bail; + + if (PyList_Append(rval, val) == -1) + goto bail; + + Py_CLEAR(val); + idx = next_idx; + + /* skip whitespace between term and , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + + /* bail if the array is closed or we didn't get the , delimiter */ + if (idx > end_idx) break; + if (str[idx] == ']') { + break; + } + else if (str[idx] != ',') { + raise_errmsg("Expecting , delimiter", pystr, idx); + goto bail; + } + idx++; + + /* skip whitespace after , */ + while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++; + } + } + + /* verify that idx < end_idx, str[idx] should be ']' */ + if (idx > end_idx || str[idx] != ']') { + raise_errmsg("Expecting object", pystr, end_idx); + goto bail; + } + *next_idx_ptr = idx + 1; + return rval; +bail: + Py_XDECREF(val); + Py_DECREF(rval); + return NULL; +} + +static PyObject * +_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) { + /* Read a JSON constant from PyString pystr. + constant is the constant string that was found + ("NaN", "Infinity", "-Infinity"). + idx is the index of the first character of the constant + *next_idx_ptr is a return-by-reference index to the first character after + the constant. + + Returns the result of parse_constant + */ + PyObject *cstr; + PyObject *rval; + /* constant is "NaN", "Infinity", or "-Infinity" */ + cstr = PyString_InternFromString(constant); + if (cstr == NULL) + return NULL; + + /* rval = parse_constant(constant) */ + rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL); + idx += PyString_GET_SIZE(cstr); + Py_DECREF(cstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyString pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + + /* save the index of the 'e' or 'E' just in case we need to backtrack */ + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyString_FromStringAndSize(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); + } + } + else { + /* parse as an int using a fast path if available, otherwise call user defined method */ + if (s->parse_int != (PyObject *)&PyInt_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + else { + rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10); + } + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) { + /* Read a JSON number from PyUnicode pystr. + idx is the index of the first character of the number + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of that number: + PyInt, PyLong, or PyFloat. + May return other types if parse_int or parse_float are set + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1; + Py_ssize_t idx = start; + int is_float = 0; + PyObject *rval; + PyObject *numstr; + + /* read a sign if it's there, make sure it's not the end of the string */ + if (str[idx] == '-') { + idx++; + if (idx > end_idx) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + } + + /* read as many integer digits as we find as long as it doesn't start with 0 */ + if (str[idx] >= '1' && str[idx] <= '9') { + idx++; + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + /* if it starts with 0 we only expect one integer digit */ + else if (str[idx] == '0') { + idx++; + } + /* no integer digits, error */ + else { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + /* if the next char is '.' followed by a digit then read all float digits */ + if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') { + is_float = 1; + idx += 2; + while (idx < end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + } + + /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */ + if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) { + Py_ssize_t e_start = idx; + idx++; + + /* read an exponent sign if present */ + if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++; + + /* read all digits */ + while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++; + + /* if we got a digit, then parse as float. if not, backtrack */ + if (str[idx - 1] >= '0' && str[idx - 1] <= '9') { + is_float = 1; + } + else { + idx = e_start; + } + } + + /* copy the section we determined to be a number */ + numstr = PyUnicode_FromUnicode(&str[start], idx - start); + if (numstr == NULL) + return NULL; + if (is_float) { + /* parse as a float using a fast path if available, otherwise call user defined method */ + if (s->parse_float != (PyObject *)&PyFloat_Type) { + rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL); + } + else { + rval = PyFloat_FromString(numstr, NULL); + } + } + else { + /* no fast path for unicode -> int, just call */ + rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL); + } + Py_DECREF(numstr); + *next_idx_ptr = idx; + return rval; +} + +static PyObject * +scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyString pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + char *str = PyString_AS_STRING(pystr); + Py_ssize_t length = PyString_GET_SIZE(pystr); + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + switch (str[idx]) { + case '"': + /* string */ + return scanstring_str(pystr, idx + 1, + PyString_AS_STRING(s->encoding), + PyObject_IsTrue(s->strict), + next_idx_ptr); + case '{': + /* object */ + return _parse_object_str(s, pystr, idx + 1, next_idx_ptr); + case '[': + /* array */ + return _parse_array_str(s, pystr, idx + 1, next_idx_ptr); + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + return Py_None; + } + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + return Py_True; + } + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + return Py_False; + } + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + return _parse_constant(s, "NaN", idx, next_idx_ptr); + } + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + return _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + return _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + break; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + return _match_number_str(s, pystr, idx, next_idx_ptr); +} + +static PyObject * +scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) +{ + /* Read one JSON term (of any kind) from PyUnicode pystr. + idx is the index of the first character of the term + *next_idx_ptr is a return-by-reference index to the first character after + the number. + + Returns a new PyObject representation of the term. + */ + Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr); + Py_ssize_t length = PyUnicode_GET_SIZE(pystr); + if (idx >= length) { + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + switch (str[idx]) { + case '"': + /* string */ + return scanstring_unicode(pystr, idx + 1, + PyObject_IsTrue(s->strict), + next_idx_ptr); + case '{': + /* object */ + return _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr); + case '[': + /* array */ + return _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr); + case 'n': + /* null */ + if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') { + Py_INCREF(Py_None); + *next_idx_ptr = idx + 4; + return Py_None; + } + break; + case 't': + /* true */ + if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') { + Py_INCREF(Py_True); + *next_idx_ptr = idx + 4; + return Py_True; + } + break; + case 'f': + /* false */ + if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') { + Py_INCREF(Py_False); + *next_idx_ptr = idx + 5; + return Py_False; + } + break; + case 'N': + /* NaN */ + if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') { + return _parse_constant(s, "NaN", idx, next_idx_ptr); + } + break; + case 'I': + /* Infinity */ + if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') { + return _parse_constant(s, "Infinity", idx, next_idx_ptr); + } + break; + case '-': + /* -Infinity */ + if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') { + return _parse_constant(s, "-Infinity", idx, next_idx_ptr); + } + break; + } + /* Didn't find a string, object, array, or named constant. Look for a number. */ + return _match_number_unicode(s, pystr, idx, next_idx_ptr); +} + +static PyObject * +scanner_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to scan_once_{str,unicode} */ + PyObject *pystr; + PyObject *rval; + Py_ssize_t idx; + Py_ssize_t next_idx = -1; + static char *kwlist[] = {"string", "idx", NULL}; + PyScannerObject *s; + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx)) + return NULL; + + if (PyString_Check(pystr)) { + rval = scan_once_str(s, pystr, idx, &next_idx); + } + else if (PyUnicode_Check(pystr)) { + rval = scan_once_unicode(s, pystr, idx, &next_idx); + } + else { + PyErr_Format(PyExc_TypeError, + "first argument must be a string, not %.80s", + Py_TYPE(pystr)->tp_name); + return NULL; + } + return _build_rval_index_tuple(rval, next_idx); +} + +static int +scanner_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Initialize Scanner object */ + PyObject *ctx; + static char *kwlist[] = {"context", NULL}; + PyScannerObject *s; + + assert(PyScanner_Check(self)); + s = (PyScannerObject *)self; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx)) + return -1; + + s->encoding = NULL; + s->strict = NULL; + s->object_hook = NULL; + s->parse_float = NULL; + s->parse_int = NULL; + s->parse_constant = NULL; + + /* PyString_AS_STRING is used on encoding */ + s->encoding = PyObject_GetAttrString(ctx, "encoding"); + if (s->encoding == Py_None) { + Py_DECREF(Py_None); + s->encoding = PyString_InternFromString(DEFAULT_ENCODING); + } + else if (PyUnicode_Check(s->encoding)) { + PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL); + Py_DECREF(s->encoding); + s->encoding = tmp; + } + if (s->encoding == NULL || !PyString_Check(s->encoding)) + goto bail; + + /* All of these will fail "gracefully" so we don't need to verify them */ + s->strict = PyObject_GetAttrString(ctx, "strict"); + if (s->strict == NULL) + goto bail; + s->object_hook = PyObject_GetAttrString(ctx, "object_hook"); + if (s->object_hook == NULL) + goto bail; + s->parse_float = PyObject_GetAttrString(ctx, "parse_float"); + if (s->parse_float == NULL) + goto bail; + s->parse_int = PyObject_GetAttrString(ctx, "parse_int"); + if (s->parse_int == NULL) + goto bail; + s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant"); + if (s->parse_constant == NULL) + goto bail; + + return 0; + +bail: + Py_CLEAR(s->encoding); + Py_CLEAR(s->strict); + Py_CLEAR(s->object_hook); + Py_CLEAR(s->parse_float); + Py_CLEAR(s->parse_int); + Py_CLEAR(s->parse_constant); + return -1; +} + +PyDoc_STRVAR(scanner_doc, "JSON scanner object"); + +static +PyTypeObject PyScannerType = { + PyObject_HEAD_INIT(0) + 0, /* tp_internal */ + "Scanner", /* tp_name */ + sizeof(PyScannerObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + scanner_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + scanner_call, /* tp_call */ + 0, /* tp_str */ + 0,/* PyObject_GenericGetAttr, */ /* tp_getattro */ + 0,/* PyObject_GenericSetAttr, */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + scanner_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + scanner_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + scanner_init, /* tp_init */ + 0,/* PyType_GenericAlloc, */ /* tp_alloc */ + 0,/* PyType_GenericNew, */ /* tp_new */ + 0,/* _PyObject_Del, */ /* tp_free */ +}; + +static int +encoder_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* initialize Encoder object */ + static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL}; + + PyEncoderObject *s; + PyObject *allow_nan; + + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + + s->markers = NULL; + s->defaultfn = NULL; + s->encoder = NULL; + s->indent = NULL; + s->key_separator = NULL; + s->item_separator = NULL; + s->sort_keys = NULL; + s->skipkeys = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOO:make_encoder", kwlist, + &s->markers, &s->defaultfn, &s->encoder, &s->indent, &s->key_separator, &s->item_separator, &s->sort_keys, &s->skipkeys, &allow_nan)) + return -1; + + Py_INCREF(s->markers); + Py_INCREF(s->defaultfn); + Py_INCREF(s->encoder); + Py_INCREF(s->indent); + Py_INCREF(s->key_separator); + Py_INCREF(s->item_separator); + Py_INCREF(s->sort_keys); + Py_INCREF(s->skipkeys); + s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii); + s->allow_nan = PyObject_IsTrue(allow_nan); + return 0; +} + +static PyObject * +encoder_call(PyObject *self, PyObject *args, PyObject *kwds) +{ + /* Python callable interface to encode_listencode_obj */ + static char *kwlist[] = {"obj", "_current_indent_level", NULL}; + PyObject *obj; + PyObject *rval; + Py_ssize_t indent_level; + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist, + &obj, _convertPyInt_AsSsize_t, &indent_level)) + return NULL; + rval = PyList_New(0); + if (rval == NULL) + return NULL; + if (encoder_listencode_obj(s, rval, obj, indent_level)) { + Py_DECREF(rval); + return NULL; + } + return rval; +} + +static PyObject * +_encoded_const(PyObject *obj) +{ + /* Return the JSON string representation of None, True, False */ + if (obj == Py_None) { + static PyObject *s_null = NULL; + if (s_null == NULL) { + s_null = PyString_InternFromString("null"); + } + Py_INCREF(s_null); + return s_null; + } + else if (obj == Py_True) { + static PyObject *s_true = NULL; + if (s_true == NULL) { + s_true = PyString_InternFromString("true"); + } + Py_INCREF(s_true); + return s_true; + } + else if (obj == Py_False) { + static PyObject *s_false = NULL; + if (s_false == NULL) { + s_false = PyString_InternFromString("false"); + } + Py_INCREF(s_false); + return s_false; + } + else { + PyErr_SetString(PyExc_ValueError, "not a const"); + return NULL; + } +} + +static PyObject * +encoder_encode_float(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a PyFloat */ + double i = PyFloat_AS_DOUBLE(obj); + if (!Py_IS_FINITE(i)) { + if (!s->allow_nan) { + PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant"); + return NULL; + } + if (i > 0) { + return PyString_FromString("Infinity"); + } + else if (i < 0) { + return PyString_FromString("-Infinity"); + } + else { + return PyString_FromString("NaN"); + } + } + /* Use a better float format here? */ + return PyObject_Repr(obj); +} + +static PyObject * +encoder_encode_string(PyEncoderObject *s, PyObject *obj) +{ + /* Return the JSON representation of a string */ + if (s->fast_encode) + return py_encode_basestring_ascii(NULL, obj); + else + return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL); +} + +static int +_steal_list_append(PyObject *lst, PyObject *stolen) +{ + /* Append stolen and then decrement its reference count */ + int rval = PyList_Append(lst, stolen); + Py_DECREF(stolen); + return rval; +} + +static int +encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level) +{ + /* Encode Python object obj to a JSON term, rval is a PyList */ + PyObject *newobj; + int rv; + + if (obj == Py_None || obj == Py_True || obj == Py_False) { + PyObject *cstr = _encoded_const(obj); + if (cstr == NULL) + return -1; + return _steal_list_append(rval, cstr); + } + else if (PyString_Check(obj) || PyUnicode_Check(obj)) + { + PyObject *encoded = encoder_encode_string(s, obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyInt_Check(obj) || PyLong_Check(obj)) { + PyObject *encoded = PyObject_Str(obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyFloat_Check(obj)) { + PyObject *encoded = encoder_encode_float(s, obj); + if (encoded == NULL) + return -1; + return _steal_list_append(rval, encoded); + } + else if (PyList_Check(obj) || PyTuple_Check(obj)) { + return encoder_listencode_list(s, rval, obj, indent_level); + } + else if (PyDict_Check(obj)) { + return encoder_listencode_dict(s, rval, obj, indent_level); + } + else { + PyObject *ident = NULL; + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(obj); + if (ident == NULL) + return -1; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + Py_DECREF(ident); + return -1; + } + if (PyDict_SetItem(s->markers, ident, obj)) { + Py_DECREF(ident); + return -1; + } + } + newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL); + if (newobj == NULL) { + Py_XDECREF(ident); + return -1; + } + rv = encoder_listencode_obj(s, rval, newobj, indent_level); + Py_DECREF(newobj); + if (rv) { + Py_XDECREF(ident); + return -1; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) { + Py_XDECREF(ident); + return -1; + } + Py_XDECREF(ident); + } + return rv; + } +} + +static int +encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level) +{ + /* Encode Python dict dct a JSON term, rval is a PyList */ + static PyObject *open_dict = NULL; + static PyObject *close_dict = NULL; + static PyObject *empty_dict = NULL; + PyObject *kstr = NULL; + PyObject *ident = NULL; + PyObject *key, *value; + Py_ssize_t pos; + int skipkeys; + Py_ssize_t idx; + + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) { + open_dict = PyString_InternFromString("{"); + close_dict = PyString_InternFromString("}"); + empty_dict = PyString_InternFromString("{}"); + if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) + return -1; + } + if (PyDict_Size(dct) == 0) + return PyList_Append(rval, empty_dict); + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(dct); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, dct)) { + goto bail; + } + } + + if (PyList_Append(rval, open_dict)) + goto bail; + + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + + /* TODO: C speedup not implemented for sort_keys */ + + pos = 0; + skipkeys = PyObject_IsTrue(s->skipkeys); + idx = 0; + while (PyDict_Next(dct, &pos, &key, &value)) { + PyObject *encoded; + + if (PyString_Check(key) || PyUnicode_Check(key)) { + Py_INCREF(key); + kstr = key; + } + else if (PyFloat_Check(key)) { + kstr = encoder_encode_float(s, key); + if (kstr == NULL) + goto bail; + } + else if (PyInt_Check(key) || PyLong_Check(key)) { + kstr = PyObject_Str(key); + if (kstr == NULL) + goto bail; + } + else if (key == Py_True || key == Py_False || key == Py_None) { + kstr = _encoded_const(key); + if (kstr == NULL) + goto bail; + } + else if (skipkeys) { + continue; + } + else { + /* TODO: include repr of key */ + PyErr_SetString(PyExc_ValueError, "keys must be a string"); + goto bail; + } + + if (idx) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + + encoded = encoder_encode_string(s, kstr); + Py_CLEAR(kstr); + if (encoded == NULL) + goto bail; + if (PyList_Append(rval, encoded)) { + Py_DECREF(encoded); + goto bail; + } + Py_DECREF(encoded); + if (PyList_Append(rval, s->key_separator)) + goto bail; + if (encoder_listencode_obj(s, rval, value, indent_level)) + goto bail; + idx += 1; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (' ' * (_indent * _current_indent_level)) + */ + } + if (PyList_Append(rval, close_dict)) + goto bail; + return 0; + +bail: + Py_XDECREF(kstr); + Py_XDECREF(ident); + return -1; +} + + +static int +encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level) +{ + /* Encode Python list seq to a JSON term, rval is a PyList */ + static PyObject *open_array = NULL; + static PyObject *close_array = NULL; + static PyObject *empty_array = NULL; + PyObject *ident = NULL; + PyObject *s_fast = NULL; + Py_ssize_t num_items; + PyObject **seq_items; + Py_ssize_t i; + + if (open_array == NULL || close_array == NULL || empty_array == NULL) { + open_array = PyString_InternFromString("["); + close_array = PyString_InternFromString("]"); + empty_array = PyString_InternFromString("[]"); + if (open_array == NULL || close_array == NULL || empty_array == NULL) + return -1; + } + ident = NULL; + s_fast = PySequence_Fast(seq, "_iterencode_list needs a sequence"); + if (s_fast == NULL) + return -1; + num_items = PySequence_Fast_GET_SIZE(s_fast); + if (num_items == 0) { + Py_DECREF(s_fast); + return PyList_Append(rval, empty_array); + } + + if (s->markers != Py_None) { + int has_key; + ident = PyLong_FromVoidPtr(seq); + if (ident == NULL) + goto bail; + has_key = PyDict_Contains(s->markers, ident); + if (has_key) { + if (has_key != -1) + PyErr_SetString(PyExc_ValueError, "Circular reference detected"); + goto bail; + } + if (PyDict_SetItem(s->markers, ident, seq)) { + goto bail; + } + } + + seq_items = PySequence_Fast_ITEMS(s_fast); + if (PyList_Append(rval, open_array)) + goto bail; + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level += 1; + /* + newline_indent = '\n' + (' ' * (_indent * _current_indent_level)) + separator = _item_separator + newline_indent + buf += newline_indent + */ + } + for (i = 0; i < num_items; i++) { + PyObject *obj = seq_items[i]; + if (i) { + if (PyList_Append(rval, s->item_separator)) + goto bail; + } + if (encoder_listencode_obj(s, rval, obj, indent_level)) + goto bail; + } + if (ident != NULL) { + if (PyDict_DelItem(s->markers, ident)) + goto bail; + Py_CLEAR(ident); + } + if (s->indent != Py_None) { + /* TODO: DOES NOT RUN */ + indent_level -= 1; + /* + yield '\n' + (' ' * (_indent * _current_indent_level)) + */ + } + if (PyList_Append(rval, close_array)) + goto bail; + Py_DECREF(s_fast); + return 0; + +bail: + Py_XDECREF(ident); + Py_DECREF(s_fast); + return -1; +} + +static void +encoder_dealloc(PyObject *self) +{ + /* Deallocate Encoder */ + PyEncoderObject *s; + assert(PyEncoder_Check(self)); + s = (PyEncoderObject *)self; + Py_CLEAR(s->markers); + Py_CLEAR(s->defaultfn); + Py_CLEAR(s->encoder); + Py_CLEAR(s->indent); + Py_CLEAR(s->key_separator); + Py_CLEAR(s->item_separator); + Py_CLEAR(s->sort_keys); + Py_CLEAR(s->skipkeys); + self->ob_type->tp_free(self); +} + +PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable"); + +static +PyTypeObject PyEncoderType = { + PyObject_HEAD_INIT(0) + 0, /* tp_internal */ + "Encoder", /* tp_name */ + sizeof(PyEncoderObject), /* tp_basicsize */ + 0, /* tp_itemsize */ + encoder_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + encoder_call, /* tp_call */ + 0, /* tp_str */ + 0,/* PyObject_GenericGetAttr, */ /* tp_getattro */ + 0,/* PyObject_GenericSetAttr, */ /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + encoder_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + encoder_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + encoder_init, /* tp_init */ + 0,/* PyType_GenericAlloc, */ /* tp_alloc */ + 0,/* PyType_GenericNew, */ /* tp_new */ + 0,/* _PyObject_Del, */ /* tp_free */ +}; + +static PyMethodDef speedups_methods[] = { + {"encode_basestring_ascii", + (PyCFunction)py_encode_basestring_ascii, + METH_O, + pydoc_encode_basestring_ascii}, + {"scanstring", + (PyCFunction)py_scanstring, + METH_VARARGS, + pydoc_scanstring}, + {NULL, NULL, 0, NULL} +}; + +PyDoc_STRVAR(module_doc, +"simplejson speedups\n"); + +void +init_speedups(void) +{ + PyObject *m; + PyScannerType.tp_getattro = PyObject_GenericGetAttr; + PyScannerType.tp_setattro = PyObject_GenericSetAttr; + PyScannerType.tp_alloc = PyType_GenericAlloc; + PyScannerType.tp_new = PyType_GenericNew; + PyScannerType.tp_free = _PyObject_Del; + if (PyType_Ready(&PyScannerType) < 0) + return; + PyEncoderType.tp_getattro = PyObject_GenericGetAttr; + PyEncoderType.tp_setattro = PyObject_GenericSetAttr; + PyEncoderType.tp_alloc = PyType_GenericAlloc; + PyEncoderType.tp_new = PyType_GenericNew; + PyEncoderType.tp_free = _PyObject_Del; + if (PyType_Ready(&PyEncoderType) < 0) + return; + m = Py_InitModule3("_speedups", speedups_methods, module_doc); + Py_INCREF((PyObject*)&PyScannerType); + PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType); + Py_INCREF((PyObject*)&PyEncoderType); + PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType); +} diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/simplejson/tool.py /tmp/7opw10sJTP/python-twitter-0.6/simplejson/tool.py --- python-twitter-0.5/simplejson/tool.py 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/simplejson/tool.py 2009-02-25 16:49:03.000000000 +0000 @@ -0,0 +1,35 @@ +r"""Using simplejson from the shell to validate and +pretty-print:: + + $ echo '{"json":"obj"}' | python -msimplejson.tool + { + "json": "obj" + } + $ echo '{ 1.2:3.4}' | python -msimplejson.tool + Expecting property name: line 1 column 2 (char 2) +""" +import simplejson + +def main(): + import sys + if len(sys.argv) == 1: + infile = sys.stdin + outfile = sys.stdout + elif len(sys.argv) == 2: + infile = open(sys.argv[1], 'rb') + outfile = sys.stdout + elif len(sys.argv) == 3: + infile = open(sys.argv[1], 'rb') + outfile = open(sys.argv[2], 'wb') + else: + raise SystemExit("%s [infile [outfile]]" % (sys.argv[0],)) + try: + obj = simplejson.load(infile) + except ValueError, e: + raise SystemExit(e) + simplejson.dump(obj, outfile, sort_keys=True, indent=4) + outfile.write('\n') + + +if __name__ == '__main__': + main() diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/featured.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/featured.json --- python-twitter-0.5/testdata/featured.json 2007-06-03 21:44:18.000000000 +0100 +++ python-twitter-0.6/testdata/featured.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"name":"Steven Wright","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/5819362\/normal\/sw.jpg?1178499811","screen_name":"stevenwright","description":"Every day, one quote from me.","location":"","url":null,"id":5819362,"protected":false,"status":{"created_at":"Fri Jun 01 16:06:28 +0000 2007","text":"I'm addicted to placebos. I could quit but it wouldn't matter.","id":86991742}},{"name":"Justine","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/7846\/normal\/iJustine_100x100.jpg?1174864273","screen_name":"ijustine","description":"I am the internet.","location":"Pittsburgh, PA","url":"http:\/\/www.tastyblogsnack.com","id":7846,"protected":false,"status":{"created_at":"Sun Jun 03 19:57:09 +0000 2007","text":"Please help me wake up Starbucks.","id":89591842}},{"name":"timer","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/5997662\/normal\/timer.gif?1179331208","screen_name":"timer","description":"","location":"","url":"http:\/\/retweet.com\/timer","id":5997662,"protected":false,"status":{"created_at":"Wed May 16 16:13:44 +0000 2007","text":"Need to remember something? Send me a direct message, and I'll tweet you back. For example, 'd timer 45 call mom' reminds you in 45 minutes.","id":66100582}},{"name":"R1 Big Weekend","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/5962712\/normal\/r1logo_twitter.jpg?1179139696","screen_name":"r1bigweekend","description":null,"location":null,"url":null,"id":5962712,"protected":false,"status":{"created_at":"Sun May 20 21:55:23 +0000 2007","text":"Thanks Preston. The site is quickly getting packed up now. It's dark and cold outside.","id":71673422}},{"name":"Scott Hanselman","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/5676102\/normal\/scott48x48.jpg?1177998926","screen_name":"shanselman","description":null,"location":null,"url":null,"id":5676102,"protected":false,"status":{"created_at":"Wed May 30 17:35:25 +0000 2007","text":"Blood sugar is 110. Great way to start the day. Ready for breakfast.","id":84275702}},{"name":"Jodrell Bank","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/5747502\/normal\/twitter_jodrellbank.png?1178216103","screen_name":"jodrellbank","description":"Home of the world's third-largest steerable radio telescope, and the MERLIN National Facility. Part of the Univ. of Manchester","location":"53.236057, -2.306871","url":"http:\/\/www.manchester.ac.uk\/jodrellbank\/","id":5747502,"protected":false,"status":{"created_at":"Sat Jun 02 13:30:00 +0000 2007","text":"Getting ready to bounce poems off the Moon and pick up their echoes with the Telescope as part of the First Move Festival on June 15-17","id":88102552}},{"name":"TwitLit","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/5588242\/normal\/twittericon.png?1178498116","screen_name":"TwitterLit","description":"Twittering the first lines of books so you don't have to. SEE ALSO: TwitterLitUK - TwitterLitCA - TwitterLitNews. [To comment, d. msg. me or contact via site.]","location":"The Stacks","url":"http:\/\/twitterlit.com","id":5588242,"protected":false,"status":{"created_at":"Sun Jun 03 09:00:46 +0000 2007","text":"\"On the drive from the suburbs to the city, we'd experienced a disturbing number of memory lapses\" http:\/\/tweetl.com\/t0","id":89035512}},{"name":"BlogPhiladelphia","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/5724872\/normal\/blogphiladelphia_logo.gif?1178142536","screen_name":"BlogPhilly","description":"A Social Media UnConference in Philly","location":"Philly!","url":"http:\/\/blogphiladelphia.net","id":5724872,"protected":false,"status":{"created_at":"Sun Jun 03 20:32:29 +0000 2007","text":"wooohoooo home in nolibs the land of indie rock tight jeans and snark aplenty. good to be back","id":89621942}},{"name":"Lisa@FashWEEK","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/5291252\/normal\/TWITTER.jpg?1177044270","screen_name":"OzFashionWeek","description":"Fashion, Beauty + Style editor for News.com.au","location":"Sydney","url":"http:\/\/www.news.com.au\/entertainment\/feature\/0,,5012703,00.html","id":5291252,"protected":false,"status":{"created_at":"Tue May 29 06:24:23 +0000 2007","text":"spent today getting distracted by net-a-porter.com ... that's a dangerous site for a fashion addict!","id":82160232}},{"name":"palm pictures","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/4260991\/normal\/cometohillary.gif?1176335676","screen_name":"palmpictures","description":"Independent Music, Film, and Love!","location":"New York, NY","url":"http:\/\/www.palmpictures.com","id":4260991,"protected":false,"status":{"created_at":"Sun Jun 03 01:16:22 +0000 2007","text":"loves our Book Expo booth neighbors from Endless Games. check em out http:\/\/endlessgames.com\/gamesmen.html","id":88685562}},{"name":"Barack Obama","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/813286\/normal\/iconbg.jpg?1177633039","screen_name":"BarackObama","description":"","location":"Chicago, IL","url":"http:\/\/www.barackobama.com","id":813286,"protected":false,"status":{"created_at":"Sun Jun 03 19:49:52 +0000 2007","text":"Heading to Democratic presidential debate at St. Anselm College in New Hampshire. Debate starts at 7pm EST and will air live on CNN.","id":89585682}},{"name":"Hollywood.com Live","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/4455001\/normal\/H_48x48c.gif?1177618725","screen_name":"hollywoodcom","description":"Hollywood.com Live @ The Tribeca Film Festival","location":"New York, NY","url":"http:\/\/fansites.hollywood.com\/live.html","id":4455001,"protected":false,"status":{"created_at":"Thu May 24 02:29:35 +0000 2007","text":"Blake, where are you now?! We'll miss you nxt wk... Go IDOLS! ... See you both over the rainbow!","id":76045122}},{"name":"Veronica","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/10350\/normal\/mypictr_140x144.jpg?1179253942","screen_name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http:\/\/www.veronicabelmont.com","id":10350,"protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212}},{"name":"Alison","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/5248441\/normal\/new1.jpg?1177002287","screen_name":"AFineFrenzy","description":"","location":"Los Angeles","url":"http:\/\/www.myspace.com\/afinefrenzy","id":5248441,"protected":false,"status":{"created_at":"Sat Jun 02 06:52:41 +0000 2007","text":"the sea took my sunglasses, i took a boatload of sand home in my bikini. fair trade? the moon is orange. my skin is still warm from the sun.","id":87808312}},{"name":"Rocketboom","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/1502111\/normal\/rocket.jpg?1174312502","screen_name":"Rocketboom","description":"Daily","location":"New York City","url":"http:\/\/www.rocketboom.com","id":1502111,"protected":false,"status":{"created_at":"Sat Jun 02 15:33:06 +0000 2007","text":"Ultimate Frisbee 2.0 in Central Park today on Sat. @ 4pm: http:\/\/groups.google.com\/group\/frisbee2point0\/","id":88218242}},{"name":"TrippingOnWords","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/59003\/normal\/lara_and_claire_BIG_HAIR.jpg?1171962588","screen_name":"TrippingOnWords","description":"Training with 200 Kenyan Orphans for the Hope Runs Marathon and 10K. As a new non profit, Hope Runs is looking for help\u2026check us out at TrippingOnWords.com!","location":"Kenya","url":"http:\/\/TrippingOnWords.com","id":59003,"protected":false,"status":{"created_at":"Sat Jun 02 18:55:59 +0000 2007","text":"heading to bed early...visiting Manager's church tomorrow. an early start is essential, apparently","id":88405002}},{"name":"Web 2.0 Expo","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/4200861\/normal\/webex2007_logo_square.jpg?1176317215","screen_name":"w2e","description":"Official twitter for the Web 2.0 Expo","location":"san francisco, ca","url":"http:\/\/blog.web2expo.com","id":4200861,"protected":false,"status":{"created_at":"Thu Apr 19 01:00:17 +0000 2007","text":"Thanks for coming! \r\nWine being brought to web2open in secs.","id":32588451}},{"name":"Jessica Mellott","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/3658381\/normal\/Jessica_Mellott.jpg?1177685550","screen_name":"JessicaMellott","description":"Teen pop singer www.jessicamellott.com www.myspace.com\/jessicamellott","location":"Maryland","url":"http:\/\/www.jessicamellott.com","id":3658381,"protected":false,"status":{"created_at":"Fri Jun 01 23:36:46 +0000 2007","text":"Getting ready for grad party weekend!","id":87429882}},{"name":"Drive","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3946281\/normal\/nathan.png?1176155112","screen_name":"foxdrive","description":"Action-fueled drama about an illegal, underground cross-country road race. Director Greg Yaitanes will Twitter live director's commentary starting Sunday 8\/7c.","location":"","url":null,"id":3946281,"protected":false,"status":{"created_at":"Tue Apr 17 04:05:53 +0000 2007","text":"twitter and tell me what you thought of the episode tonight.","id":30603561}},{"name":"Status Updates","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/46413\/normal\/maintenance.gif?1171961490","screen_name":"twitter_status","description":"140 characters or less on the health of Twitter!","location":"Inside the Twitter","url":null,"id":46413,"protected":false,"status":{"created_at":"Tue May 29 07:54:18 +0000 2007","text":"Catching up on processing updates after some brief confusion.","id":82226192}}] \ No newline at end of file +[{"name":"Steven Wright","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/5819362/normal/sw.jpg?1178499811","screen_name":"stevenwright","description":"Every day, one quote from me.","location":"","url":null,"id":5819362,"protected":false,"status":{"created_at":"Fri Jun 01 16:06:28 +0000 2007","text":"I'm addicted to placebos. I could quit but it wouldn't matter.","id":86991742}},{"name":"Justine","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/7846/normal/iJustine_100x100.jpg?1174864273","screen_name":"ijustine","description":"I am the internet.","location":"Pittsburgh, PA","url":"http://www.tastyblogsnack.com","id":7846,"protected":false,"status":{"created_at":"Sun Jun 03 19:57:09 +0000 2007","text":"Please help me wake up Starbucks.","id":89591842}},{"name":"timer","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/5997662/normal/timer.gif?1179331208","screen_name":"timer","description":"","location":"","url":"http://retweet.com/timer","id":5997662,"protected":false,"status":{"created_at":"Wed May 16 16:13:44 +0000 2007","text":"Need to remember something? Send me a direct message, and I'll tweet you back. For example, 'd timer 45 call mom' reminds you in 45 minutes.","id":66100582}},{"name":"R1 Big Weekend","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/5962712/normal/r1logo_twitter.jpg?1179139696","screen_name":"r1bigweekend","description":null,"location":null,"url":null,"id":5962712,"protected":false,"status":{"created_at":"Sun May 20 21:55:23 +0000 2007","text":"Thanks Preston. The site is quickly getting packed up now. It's dark and cold outside.","id":71673422}},{"name":"Scott Hanselman","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/5676102/normal/scott48x48.jpg?1177998926","screen_name":"shanselman","description":null,"location":null,"url":null,"id":5676102,"protected":false,"status":{"created_at":"Wed May 30 17:35:25 +0000 2007","text":"Blood sugar is 110. Great way to start the day. Ready for breakfast.","id":84275702}},{"name":"Jodrell Bank","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/5747502/normal/twitter_jodrellbank.png?1178216103","screen_name":"jodrellbank","description":"Home of the world's third-largest steerable radio telescope, and the MERLIN National Facility. Part of the Univ. of Manchester","location":"53.236057, -2.306871","url":"http://www.manchester.ac.uk/jodrellbank/","id":5747502,"protected":false,"status":{"created_at":"Sat Jun 02 13:30:00 +0000 2007","text":"Getting ready to bounce poems off the Moon and pick up their echoes with the Telescope as part of the First Move Festival on June 15-17","id":88102552}},{"name":"TwitLit","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/5588242/normal/twittericon.png?1178498116","screen_name":"TwitterLit","description":"Twittering the first lines of books so you don't have to. SEE ALSO: TwitterLitUK - TwitterLitCA - TwitterLitNews. [To comment, d. msg. me or contact via site.]","location":"The Stacks","url":"http://twitterlit.com","id":5588242,"protected":false,"status":{"created_at":"Sun Jun 03 09:00:46 +0000 2007","text":"\"On the drive from the suburbs to the city, we'd experienced a disturbing number of memory lapses\" http://tweetl.com/t0","id":89035512}},{"name":"BlogPhiladelphia","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/5724872/normal/blogphiladelphia_logo.gif?1178142536","screen_name":"BlogPhilly","description":"A Social Media UnConference in Philly","location":"Philly!","url":"http://blogphiladelphia.net","id":5724872,"protected":false,"status":{"created_at":"Sun Jun 03 20:32:29 +0000 2007","text":"wooohoooo home in nolibs the land of indie rock tight jeans and snark aplenty. good to be back","id":89621942}},{"name":"Lisa@FashWEEK","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/5291252/normal/TWITTER.jpg?1177044270","screen_name":"OzFashionWeek","description":"Fashion, Beauty + Style editor for News.com.au","location":"Sydney","url":"http://www.news.com.au/entertainment/feature/0,,5012703,00.html","id":5291252,"protected":false,"status":{"created_at":"Tue May 29 06:24:23 +0000 2007","text":"spent today getting distracted by net-a-porter.com ... that's a dangerous site for a fashion addict!","id":82160232}},{"name":"palm pictures","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/4260991/normal/cometohillary.gif?1176335676","screen_name":"palmpictures","description":"Independent Music, Film, and Love!","location":"New York, NY","url":"http://www.palmpictures.com","id":4260991,"protected":false,"status":{"created_at":"Sun Jun 03 01:16:22 +0000 2007","text":"loves our Book Expo booth neighbors from Endless Games. check em out http://endlessgames.com/gamesmen.html","id":88685562}},{"name":"Barack Obama","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/813286/normal/iconbg.jpg?1177633039","screen_name":"BarackObama","description":"","location":"Chicago, IL","url":"http://www.barackobama.com","id":813286,"protected":false,"status":{"created_at":"Sun Jun 03 19:49:52 +0000 2007","text":"Heading to Democratic presidential debate at St. Anselm College in New Hampshire. Debate starts at 7pm EST and will air live on CNN.","id":89585682}},{"name":"Hollywood.com Live","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/4455001/normal/H_48x48c.gif?1177618725","screen_name":"hollywoodcom","description":"Hollywood.com Live @ The Tribeca Film Festival","location":"New York, NY","url":"http://fansites.hollywood.com/live.html","id":4455001,"protected":false,"status":{"created_at":"Thu May 24 02:29:35 +0000 2007","text":"Blake, where are you now?! We'll miss you nxt wk... Go IDOLS! ... See you both over the rainbow!","id":76045122}},{"name":"Veronica","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/10350/normal/mypictr_140x144.jpg?1179253942","screen_name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http://www.veronicabelmont.com","id":10350,"protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212}},{"name":"Alison","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/5248441/normal/new1.jpg?1177002287","screen_name":"AFineFrenzy","description":"","location":"Los Angeles","url":"http://www.myspace.com/afinefrenzy","id":5248441,"protected":false,"status":{"created_at":"Sat Jun 02 06:52:41 +0000 2007","text":"the sea took my sunglasses, i took a boatload of sand home in my bikini. fair trade? the moon is orange. my skin is still warm from the sun.","id":87808312}},{"name":"Rocketboom","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/1502111/normal/rocket.jpg?1174312502","screen_name":"Rocketboom","description":"Daily","location":"New York City","url":"http://www.rocketboom.com","id":1502111,"protected":false,"status":{"created_at":"Sat Jun 02 15:33:06 +0000 2007","text":"Ultimate Frisbee 2.0 in Central Park today on Sat. @ 4pm: http://groups.google.com/group/frisbee2point0/","id":88218242}},{"name":"TrippingOnWords","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/59003/normal/lara_and_claire_BIG_HAIR.jpg?1171962588","screen_name":"TrippingOnWords","description":"Training with 200 Kenyan Orphans for the Hope Runs Marathon and 10K. As a new non profit, Hope Runs is looking for help\u2026check us out at TrippingOnWords.com!","location":"Kenya","url":"http://TrippingOnWords.com","id":59003,"protected":false,"status":{"created_at":"Sat Jun 02 18:55:59 +0000 2007","text":"heading to bed early...visiting Manager's church tomorrow. an early start is essential, apparently","id":88405002}},{"name":"Web 2.0 Expo","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/4200861/normal/webex2007_logo_square.jpg?1176317215","screen_name":"w2e","description":"Official twitter for the Web 2.0 Expo","location":"san francisco, ca","url":"http://blog.web2expo.com","id":4200861,"protected":false,"status":{"created_at":"Thu Apr 19 01:00:17 +0000 2007","text":"Thanks for coming! \r\nWine being brought to web2open in secs.","id":32588451}},{"name":"Jessica Mellott","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/3658381/normal/Jessica_Mellott.jpg?1177685550","screen_name":"JessicaMellott","description":"Teen pop singer www.jessicamellott.com www.myspace.com/jessicamellott","location":"Maryland","url":"http://www.jessicamellott.com","id":3658381,"protected":false,"status":{"created_at":"Fri Jun 01 23:36:46 +0000 2007","text":"Getting ready for grad party weekend!","id":87429882}},{"name":"Drive","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3946281/normal/nathan.png?1176155112","screen_name":"foxdrive","description":"Action-fueled drama about an illegal, underground cross-country road race. Director Greg Yaitanes will Twitter live director's commentary starting Sunday 8/7c.","location":"","url":null,"id":3946281,"protected":false,"status":{"created_at":"Tue Apr 17 04:05:53 +0000 2007","text":"twitter and tell me what you thought of the episode tonight.","id":30603561}},{"name":"Status Updates","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/46413/normal/maintenance.gif?1171961490","screen_name":"twitter_status","description":"140 characters or less on the health of Twitter!","location":"Inside the Twitter","url":null,"id":46413,"protected":false,"status":{"created_at":"Tue May 29 07:54:18 +0000 2007","text":"Catching up on processing updates after some brief confusion.","id":82226192}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/followers.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/followers.json --- python-twitter-0.5/testdata/followers.json 2007-06-03 21:12:17.000000000 +0100 +++ python-twitter-0.6/testdata/followers.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"name":"Robert Brook","description":null,"location":"London","url":"http:\/\/www.druidstreet.com\/","id":5567,"protected":false,"status":{"created_at":"Sun Jun 03 19:56:54 +0000 2007","text":"poking greader trends - some must live, some must die","id":89591602},"profile_image_url":"http:\/\/assets1.twitter.com\/images\/default_image.gif?1180755379","screen_name":"robertbrook"},{"name":"cote","description":"Industry analyst with RedMonk. DrunkAndRetired.com. YUH!","location":"Austin, Texas","url":"http:\/\/www.peopleoverprocess.com\/","id":53953,"protected":false,"status":{"created_at":"Sun Jun 03 19:54:55 +0000 2007","text":"Flight to ORD delayed 2 hours. This can't be good.","id":89589762},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/53953\/normal\/square-lil-hat.jpg?1171962174","screen_name":"cote"},{"name":"Moby","description":"I'm here. Now what?","location":"San Francisco","url":"http:\/\/blog.mobius.name","id":4296211,"protected":false,"status":{"created_at":"Sun Jun 03 19:46:00 +0000 2007","text":"Responding to hate mail. \"Ignunce\" at its finest","id":89583032},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/4296211\/normal\/mobycycle.031007.jpg?1176350283","screen_name":"ibod8x5"},{"name":"Alex King","description":"","location":"Denver, CO","url":"http:\/\/alexking.org","id":101143,"protected":false,"status":{"created_at":"Sun Jun 03 19:06:54 +0000 2007","text":"@andrewhyde Coverage was poor enough in the area to drive me back to Sprint.","id":89554432},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/101143\/normal\/alex_king.jpg?1171953145","screen_name":"alexkingorg"},{"name":"Patrick Mueller","description":"Graying IBMer working for the WebSphere organization","location":"The Triangle, NC","url":"http:\/\/muellerware.org","id":765080,"protected":false,"status":{"created_at":"Sun Jun 03 18:48:51 +0000 2007","text":"@JoshStaiger that was one I was looking at, which Google (coincidentally?) ranked highly. Wondering how far I can get w\/avacado & salsa","id":89537732},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/765080\/normal\/glasses-down-115x115-gradient.jpg?1177726044","screen_name":"pmuellr"},{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"status":{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","screen_name":"kesuke"},{"name":"Jeff Barr","description":"Amazon Web Services Evangelist, Blogger, Father of 5.","location":"Sammamish, Washington, USA","url":"http:\/\/www.jeff-barr.com","id":48443,"protected":false,"status":{"created_at":"Sun Jun 03 16:43:47 +0000 2007","text":"Preparing for trip to DC tomorrow AM - lots of reading materials, TODO list, iPod fresh, camera charged, laptop packed. Arrange for taxi.","id":89432112},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/48443\/normal\/jeff_barr.jpg?1171961668","screen_name":"jeffbarr"},{"name":"Paul Downey","description":"Computing Industry Bi-product","location":"Berkhamsted, UK","url":"http:\/\/blog.whatfettle.com","id":13486,"protected":false,"status":{"created_at":"Sun Jun 03 16:24:53 +0000 2007","text":"back from tea and cake at the village hall now resuming futile search for a family holiday on 'tinternet","id":89413652},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/13486\/normal\/psd-75x75.jpg?1171954493","screen_name":"psd"},{"name":"C Ortiz","description":"Amongst other things that keep me far away from a computer, I manage PrivateMilitary.org","location":"UK | US","url":"http:\/\/www.privatemilitary.org\/","id":2306071,"protected":false,"status":{"created_at":"Sun Jun 03 16:10:44 +0000 2007","text":"Sunday reading: security contractors snatched without a shot: http:\/\/tinyurl.com\/2qyv6x","id":89399902},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/2306071\/normal\/photo_twitter.jpg?1177775770","screen_name":"privatemilitary"},{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"status":{"created_at":"Sun Jun 03 15:57:16 +0000 2007","text":"ah, it's under \"Reply\"","id":89385112},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"},{"name":"Alexander J Turner","description":"Hubby, Dad & Chemist - Escaped Into IT Land To Cause Damage As A Software Architect!","location":"Oxford, UK","url":"http:\/\/www.nerds-central.com","id":1621891,"protected":false,"status":{"created_at":"Sun Jun 03 15:34:24 +0000 2007","text":"@chrisborgan - Just got back from a 20mile bike ride. You're right - it is all about habits. Thanks! less than 5 seconds ago from web","id":89364302},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/1621891\/normal\/MadAlex256.png?1178292687","screen_name":"AlexTurner"},{"name":"Josh Lucas","description":"Just adding another bit of distraction...","location":"Pasadena, CA","url":"http:\/\/www.stonecottage.com\/josh\/","id":47023,"protected":false,"status":{"created_at":"Sun Jun 03 15:16:47 +0000 2007","text":"wearing my new old-skool upcoming shirt","id":89345632},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/47023\/normal\/cubs_me.jpg?1171961535","screen_name":"lucasjosh"},{"name":"jark","description":"Co-Founder of deviantART","location":"Tokyo, Japan","url":"http:\/\/jarkolicious.com\/","id":39653,"protected":false,"status":{"created_at":"Sun Jun 03 14:51:25 +0000 2007","text":"heads to bed, but first starts a process to burn 300 to DVD.","id":89318752},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/39653\/normal\/jark-static.jpg?1171960858","screen_name":"jark"},{"name":"Paul Terry Walhus","description":"developer building team for http:\/\/searchslides.com & http:\/\/web2.0slides.com","location":"Austin, Texas","url":"http:\/\/austinblogger.com\/blog\/","id":1418,"protected":false,"status":{"created_at":"Sun Jun 03 14:05:39 +0000 2007","text":"Geni - Everyone's Related: cool web 2.0 family tree app, requires login http:\/\/www.geni.com\/tree","id":89277722},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/1418\/normal\/paulterrywalhus.jpg?1175716284","screen_name":"springnet"},{"name":"Floozle","description":"","location":"","url":null,"id":58863,"protected":false,"status":{"created_at":"Sun Jun 03 12:50:02 +0000 2007","text":"Off to the office to complete the conversion","id":89202702},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/58863\/normal\/29logo3-med.gif?1174609772","screen_name":"Floozle"},{"name":" T\u00c7","description":"Chem: Physics: Math: Logic: Observation: Analysis: Hypothesis: Experimentation: Iteration: Evolution: Science. It works, bitches.","location":"San Francisco, CA","url":"http:\/\/tantek.com\/","id":11628,"protected":false,"status":{"created_at":"Sun Jun 03 10:34:10 +0000 2007","text":"pondering how 1 can b so tired & sore after exercise, yet so energized as 2 b up at 3:30am, w only *1* coffee instead of usual 3 per workday","id":89104612},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/11628\/normal\/icon200px.jpg?1171953743","screen_name":"t"},{"name":"adam","description":"http:\/\/ifindkarma.com\/","location":"Palo Alto, CA","url":"http:\/\/renkoo.com\/profile\/ee0e95249268b86ff2053bef214bfeda","id":1688,"protected":false,"status":{"created_at":"Sun Jun 03 07:07:00 +0000 2007","text":"Norman Mailer on the idea of perfect happiness: \"A fool draws a road map to his magic city.\" (Vanity Fair, Jan 2007)","id":88952182},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/1688\/normal\/RifkinIcon.jpg?1179096764","screen_name":"ifindkarma"},{"name":"Cameron Walters","description":"getting satisfaction daily","location":"San Francisco","url":"http:\/\/chuddup.com\/","id":3922,"protected":false,"status":{"created_at":"Sun Jun 03 06:54:14 +0000 2007","text":"Earlier: Pilates. Now: Spazzy thrashcore electro.","id":88940252},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/3922\/normal\/awesome.jpg?1177104664","screen_name":"ceedub"},{"name":"Jason Calacanis","description":"Looking for TNBT","location":"LA","url":"http:\/\/www.calacanis.com","id":3840,"protected":false,"status":{"created_at":"Sun Jun 03 06:49:36 +0000 2007","text":"Having dinner with loic, sam harris, jared diamond, and john brockman... Among others.","id":88935882},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3840\/normal\/jason2.jpg?1172771113","screen_name":"JasonCalacanis"},{"name":"hober","description":"One-line bios are hard.","location":"San Diego, CA","url":"http:\/\/edward.oconnor.cx\/","id":13607,"protected":false,"status":{"created_at":"Sun Jun 03 06:41:04 +0000 2007","text":"Emacs 22 released while I was at BarCamp. heh. http:\/\/www.gnu.org\/software\/emacs\/","id":88929942},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/13607\/normal\/ted-icon-large.jpg?1171954556","screen_name":"hober"},{"name":" Jay","description":"Senior Systems Administrator of the Unix variety ","location":"Houston","url":null,"id":336113,"protected":false,"status":{"created_at":"Sun Jun 03 04:36:50 +0000 2007","text":"Wonders what kind of cheese Kristie spent $70 on.","id":88843542},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/336113\/normal\/MyPicture.jpg?1179799311","screen_name":"meangrape"},{"name":"Tatsuhiko Miyagawa","description":"Yet another Perl hacker","location":"San Francisco","url":"http:\/\/bulknews.vox.com\/","id":731253,"protected":false,"status":{"created_at":"Sun Jun 03 04:06:57 +0000 2007","text":"had a good rice\/veggie\/pork noodle and shrimp fried rice in HoH. Feeling almost full and too ricey","id":88820042},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/731253\/normal\/P506iC0003735833.jpg?1170146286","screen_name":"miyagawa"},{"name":" Rod Begbie","description":"Cantankerous Scots git.","location":"Somerville, MA","url":"http:\/\/groovymother.com\/","id":761,"protected":false,"status":{"created_at":"Sun Jun 03 03:43:42 +0000 2007","text":"Fun first day in LA. Looked at stars in sidewalk, got approached by Scientologists three times, Mexican dinner with old friends. Happy!","id":88800482},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/761\/normal\/vatar.png?1167697094","screen_name":"rodbegbie"},{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http:\/\/www.veronicabelmont.com","id":10350,"protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/10350\/normal\/mypictr_140x144.jpg?1179253942","screen_name":"Veronica"},{"name":"eric L","description":"mobile expert, internet idiot","location":"san francisco","url":"http:\/\/www.n1s.net","id":8291,"protected":false,"status":{"created_at":"Sun Jun 03 03:08:04 +0000 2007","text":"A howat once said there's no scorin with the sporin.","id":88771502},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/8291\/normal\/bandit.jpg?1165079078","screen_name":"n1s"},{"name":"Mr Messina","description":"As if concentrating wasn't hard enough already.","location":"94107","url":"http:\/\/factoryjoe.com\/","id":1186,"protected":false,"status":{"created_at":"Sun Jun 03 01:42:47 +0000 2007","text":"OpenID won the disruptor award at The NextWeb conference! http:\/\/tinyurl.com\/yq8e89","id":88706172},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/1186\/normal\/devil_150.jpg?1171953828","screen_name":"factoryjoe"},{"name":"Scobleizer","description":"Tech geek blogger @ http:\/\/scobleizer.com","location":"Half Moon Bay, California, USA","url":"http:\/\/scobleshow.com","id":13348,"protected":false,"status":{"created_at":"Sun Jun 03 01:06:52 +0000 2007","text":"I can't get into my Flickr account. I can get into Yahoo, but not Flickr... http:\/\/tinyurl.com\/2b63rh","id":88677782},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/13348\/normal\/trafficlight.jpg?1175390038","screen_name":"Scobleizer"},{"name":"Andy Edmonds","description":null,"location":null,"url":null,"id":936361,"protected":false,"status":{"created_at":"Sat Jun 02 22:09:55 +0000 2007","text":"Tries out squidoo at http:\/\/www.squidoo.com\/eyetrack","id":88551872},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/936361\/normal\/andy_headshot_48x48.png?1176350570","screen_name":"andyed"},{"name":"Ian McKellar","description":"","location":"San Francisco, CA","url":"http:\/\/ian.mckellar.org\/","id":259,"protected":false,"status":{"created_at":"Sat Jun 02 20:55:30 +0000 2007","text":"lolfeeds got shut down for using too much cpu so I had to get around to adding a caching layer. it fixed some character encoding issues too!","id":88496342},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/259\/normal\/trapped.jpg?1171957894","screen_name":"ianmckellar"},{"name":"Jeremy Zawodny","description":"I fly and geek.","location":"San Jose, CA","url":"http:\/\/jeremy.zawodny.com\/blog\/","id":97933,"protected":false,"status":{"created_at":"Sat Jun 02 20:04:53 +0000 2007","text":"errands and packing for a week in the desert next week...","id":88458352},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/97933\/normal\/Zawodny-md.jpg?1166680073","screen_name":"jzawodn"},{"name":"Mihai","description":"","location":"New York, NY","url":"http:\/\/persistent.info\/","id":28203,"protected":false,"status":{"created_at":"Sat Jun 02 19:46:47 +0000 2007","text":"Back on campus for reunions. Don't feel old just yet. Phew.","id":88444532},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/28203\/normal\/mihaip.jpg?1171958069","screen_name":"mihai"},{"name":"Robert Merrill","description":"Helping People & Teams Become Better","location":"Provo, Utah","url":"http:\/\/www.utahtechjobs.com","id":755721,"protected":false,"status":{"created_at":"Sat Jun 02 17:16:45 +0000 2007","text":"Heading home","id":88316782},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/755721\/normal\/50998991_N00.jpg?1170908379","screen_name":"robertmerrill"},{"name":"Darren Kulp","description":"Computer, linguistics, and music geek. What else is there to say?","location":"Eau Claire, WI, USA","url":"http:\/\/kulp.ch\/","id":6083072,"protected":false,"status":{"created_at":"Sat Jun 02 15:39:55 +0000 2007","text":"twitterim finally works? Fancy that.","id":88225602},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/6083072\/normal\/f1.jpg?1179303021","screen_name":"kulp"},{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","url":"http:\/\/www.seancoon.org","id":677903,"protected":false,"status":{"created_at":"Sat Jun 02 08:27:03 +0000 2007","text":"Holy moly. Time to go fishing. Yeeeaaahhh!","id":87876222},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/677903\/normal\/sean-85.png?1173252480","screen_name":"spcoon"},{"name":"Niall","description":"Squeezing the most out of everything but my phone","location":"San Francisco, CA","url":"http:\/\/www.niallkennedy.com\/","id":1085,"protected":true,"status":{"created_at":"Sat Jun 02 04:32:57 +0000 2007","text":"your server migration is at 11. oh, we changed our minds, we're doing it at 9:30. ummm....yay?","id":87696902},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/1085\/normal\/niall_ringer.jpg?1171953434","screen_name":"niall"},{"name":"George P","description":"i am illicium","location":"Bay Area","url":null,"id":796724,"protected":true,"status":{"created_at":"Sat Jun 02 04:07:24 +0000 2007","text":"Hello, Twitter! Long time no see.","id":87673952},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/796724\/normal\/star.gif?1172880544","screen_name":"illicium"},{"name":"Daniel E. Renfer","description":"Freelance Individual","location":"Ypsilanti, MI","url":"http:\/\/kronkltd.net\/","id":11491,"protected":false,"status":{"created_at":"Fri Jun 01 21:15:50 +0000 2007","text":"In the immortal words of Doug from MTV's The State: \"I'm outta heere\"","id":87306192},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/11491\/normal\/886.jpg?1171953691","screen_name":"duck1123"},{"name":"Simon Willison","description":null,"location":"London","url":"http:\/\/simonwillison.net\/","id":12497,"protected":false,"status":{"created_at":"Fri Jun 01 21:01:59 +0000 2007","text":"Nat and I are in Brighton this weekend, anyone want to meet up?","id":87291372},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/12497\/normal\/298777290_a5ed9a4e70_m.jpg?1171954113","screen_name":"simonw"},{"name":"Yoz","description":"A small yoz-type object, currently residing in San Francisco","location":"San Francisco, CA","url":"http:\/\/yoz.com\/","id":12329,"protected":false,"status":{"created_at":"Fri Jun 01 18:13:23 +0000 2007","text":"@riffraff814: Thanks but no thanks, don't drink coffee, my body is a temple filled with magical prancing ponies yay peanut M&M overdose","id":87132942},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/12329\/normal\/yozlap-100.jpg?1171954052","screen_name":"yoz"},{"name":"J Scud","description":"Software can be beautiful.","location":"Silicon Valley California","url":"http:\/\/jeffreyscudder.blogspot.com\/","id":1359571,"protected":false,"status":{"created_at":"Fri Jun 01 07:14:58 +0000 2007","text":"Watching videos from Google Developer Day: http:\/\/tinyurl.com\/ywr8cz","id":86425072},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/1359571\/normal\/temp_img.jpg?1177211826","screen_name":"jaguarjaws"},{"name":"Joe Duck","description":"Travel Internet Oregon Guy","location":"Oregon","url":"http:\/\/joeduck.wordpress.com","id":150433,"protected":false,"status":{"created_at":"Fri Jun 01 07:09:02 +0000 2007","text":"Removed Google downranking on an obscure travel page. Next test - non-obscure page fixes.http:\/\/tinyurl.com\/227re8","id":86418302},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/150433\/normal\/joebiopic.jpg?1173598016","screen_name":"joeduck"},{"name":"Jon Phillips","description":"http:\/\/rejon.org\/bio\/","location":"SF","url":"http:\/\/rejon.org","id":744063,"protected":false,"status":{"created_at":"Fri Jun 01 04:59:45 +0000 2007","text":"Killing it...","id":86303992},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/744063\/normal\/jon_by_ryan_junell_fixed.png?1170353692","screen_name":"rejon"},{"name":"The BFF","description":"","location":"Netherlands","url":"http:\/\/doncrowley.blogspot.com\/","id":1620121,"protected":false,"status":{"created_at":"Thu May 31 21:30:08 +0000 2007","text":"Twitteriffic addicts - some power tips: I am amazed at how many still do not know these tricks with twitte.. http:\/\/tinyurl.com\/2mzjod","id":85901962},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/1620121\/normal\/bff.jpg?1174419110","screen_name":"TheBFF"},{"name":"Andy Armstrong","description":"Perl Bloke","location":"Cumbria, UK","url":"http:\/\/hexten.net","id":1022831,"protected":false,"status":{"created_at":"Thu May 31 18:58:03 +0000 2007","text":"37 downloads pending to iTMS. Keeps timing out. Bah. Anyone else?","id":85769802},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/1022831\/normal\/moi.jpg?1176741181","screen_name":"AndyArmstrong"},{"name":"Brian Suda","description":"SWM Informatician @64.132511;-21.906494 (microformats,GRDDL,XSLT,PHP,picoformats,XHTML)","location":"Iceland","url":"http:\/\/suda.co.uk\/","id":15313,"protected":false,"status":{"created_at":"Thu May 31 17:54:18 +0000 2007","text":"is happy that tonight is the last night that you can smoke in bars and restaurants in Iceland. Washing machines won't be happy with this.","id":85708142},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/15313\/normal\/gravatar.png?1175011623","screen_name":"briansuda"},{"name":"arunaurl","description":"Make long URLs small!","location":"All over the web","url":"http:\/\/arunaurl.com","id":6371812,"protected":false,"status":{"created_at":"Tue May 29 23:49:15 +0000 2007","text":"Aruna URL now available as a Zimbra Collaboration Suite plug-in \/ zimlet http:\/\/arunaurl.com\/b25","id":83248192},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/6371812\/normal\/Aruna_URL_logo.png?1180314185","screen_name":"arunaurl"},{"name":"Hiroshi Ayukawa","description":"A Japanese ordinary Python & C++ programmer","location":"Tokyo, Japan","url":null,"id":3786561,"protected":false,"status":{"created_at":"Tue May 29 16:22:31 +0000 2007","text":"I don't like to use JAVA any more... Let me sleep, Ahhhhh! Give me Python.","id":82802982},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3786561\/normal\/muku.jpg?1176037335","screen_name":"hiroshiykw"},{"name":" Scriptless Day","description":"","location":"","url":"http:\/\/www.scriptlessday.com","id":3641801,"protected":false,"status":{"created_at":"Mon May 28 07:55:27 +0000 2007","text":"39 Days Left till Scriptless Day!! http:\/\/www.scriptlessday.com :D","id":81305782},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/3641801\/normal\/splash2.png?1175927287","screen_name":"scriptlessday"},{"name":"brady forrest","description":null,"location":null,"url":null,"id":6140,"protected":false,"status":{"created_at":"Sat May 26 02:51:53 +0000 2007","text":"im at convergence 13 in pdx. looking for events inspiration.","id":78751852},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/6140\/normal\/IMG_9629.jpg?1175295805","screen_name":"brady"},{"name":"TV with MeeVee","description":"We heart TV","location":"Burlingame, CA","url":"http:\/\/blog.meevee.com","id":2371741,"protected":false,"status":{"created_at":"Wed May 23 17:22:20 +0000 2007","text":"Nicole Richie Denies Rehab Rumors http:\/\/tinyurl.com\/2hvn8h","id":75514922},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/2371741\/normal\/meevee_potato_ad3SoSF.jpg?1174950717","screen_name":"TVwithMeeVee"},{"name":"Emmet Connolly","description":"I have nothing to say (and I am saying it)","location":"Dublin","url":"http:\/\/blog.thoughtwax.com\/","id":11323,"protected":false,"status":{"created_at":"Tue May 22 21:34:28 +0000 2007","text":"Virtual subdomains + .htaccess = ow, my brain. Anyway, three years late but my blog now has pretty urls. Hooray!","id":74472602},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/11323\/normal\/me.jpg?1171953619","screen_name":"thoughtwax"},{"name":"Bilal Hameed","description":"Editor of Startup Meme","location":"As if it matters","url":"http:\/\/startupmeme.com","id":5629992,"protected":false,"status":{"created_at":"Tue May 15 16:41:05 +0000 2007","text":"http:\/\/tinyurl.com\/2h474q Use EasyPost To Send Snail Mail in Canada","id":65079402},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/5629992\/normal\/me.jpg?1177848235","screen_name":"startupmeme"},{"name":"Koen Sadza","description":"I study Applied Physics. My hobbies are my GF,SInging,Scouting,Clubbing,Computers","location":"Eindhoven, The Netherlands","url":"http:\/\/weblog.ksdz.nl\/","id":803238,"protected":false,"status":{"created_at":"Sat May 12 14:16:37 +0000 2007","text":"Net terug van Scouting","id":61483292},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/803238\/normal\/kerst.jpg?1172746148","screen_name":"ksdz"},{"name":"Webtickle","description":"","location":"California","url":null,"id":1322691,"protected":false,"status":{"created_at":"Thu May 10 03:25:15 +0000 2007","text":"The Freelancer\u2019s Toolset: 100 Web Apps for Everything You Will Possibly Need\" http:\/\/tinyurl.com\/2dztm8","id":58099692},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/1322691\/normal\/large4197.png?1174089345","screen_name":"Webtickle"},{"name":"Yes","description":null,"location":null,"url":null,"id":765884,"protected":false,"status":{"created_at":"Wed May 09 20:43:39 +0000 2007","text":"Whoo hoo! Yay!","id":57715832},"profile_image_url":"http:\/\/assets1.twitter.com\/images\/default_image.gif?1180755379","screen_name":"Yes"},{"name":"Leland Harding III","description":"Traditional country artist, from South Dakota with a major background in the country music scene","location":"South Dakota","url":"http:\/\/www.lelandharding.com","id":3545551,"protected":false,"status":{"created_at":"Tue May 08 08:01:31 +0000 2007","text":"Anyone who has been scammed by these people can get ahold of me at leland@ldharding.com","id":54614912},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/3545551\/normal\/image01-xml.jpg?1175797244","screen_name":"ldcountry"},{"name":"Trendio M","description":"","location":"","url":"http:\/\/www.trendio.com","id":5845532,"protected":false,"status":{"created_at":"Mon May 07 22:32:39 +0000 2007","text":"Open Source and Linux are up today. Which will be this week's top technology trends? http:\/\/tinyurl.com\/2bpknb","id":53868212},"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/5845532\/normal\/logo48x48.gif?1178576662","screen_name":"Trendio_M"},{"name":"Patrick Wang","description":"","location":"San Francisco, CA","url":"http:\/\/junesix.org","id":799123,"protected":false,"status":{"created_at":"Mon Apr 16 21:57:48 +0000 2007","text":"@jabancroft optimized builds at beatnikpad.com","id":30322371},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/799123\/normal\/samurai_head.gif?1173313806","screen_name":"junesix"},{"name":"Dion Almaer","description":"ajaxian, googley, and techno","location":"Palo Alto, CA","url":"http:\/\/almaer.com\/blog","id":4216361,"protected":false,"status":{"created_at":"Wed Apr 11 17:48:16 +0000 2007","text":"Enjoying being able to talk about the Google Developer Day!","id":24861261},"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/4216361\/normal\/logo-48x48.jpg?1176313520","screen_name":"dalmaer"},{"name":"Chris DiBona","description":null,"location":null,"url":null,"id":44423,"protected":true,"status":{"created_at":"Fri Mar 16 16:42:55 +0000 2007","text":"Cripes, trapped on the tarmac at iad. Today, we sit in hell:","id":8638201},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/44423\/normal\/103ID_DiBona.jpg?1171961329","screen_name":"cdibona"},{"name":"napoleone","description":"","location":"piacenza ITALY","url":null,"id":740133,"protected":false,"status":{"created_at":"Sat Feb 17 14:36:53 +0000 2007","text":"sono indeciso tra casual o un p\u00f2 sportive!!!","id":5555694},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/740133\/normal\/P6050088.jpg?1170800580","screen_name":"Cris"},{"name":"Brian Tucker","description":null,"location":null,"url":null,"id":759328,"protected":false,"status":{"created_at":"Sun Feb 11 04:11:11 +0000 2007","text":"Chinese new year starts next weekend I think.","id":5422032},"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/759328\/normal\/bt.png?1170987088","screen_name":"brian318"},{"name":"Josh H","description":null,"location":null,"url":null,"id":2831771,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/images\/default_image.gif?1180755379","screen_name":"josh59x"},{"name":"mfagan","description":"","location":"Canada","url":"http:\/\/faganm.com\/","id":677403,"protected":false,"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/677403\/normal\/me_with_hat.jpg?1171966071","screen_name":"mfagan"}] \ No newline at end of file +[{"name":"Robert Brook","description":null,"location":"London","url":"http://www.druidstreet.com/","id":5567,"protected":false,"status":{"created_at":"Sun Jun 03 19:56:54 +0000 2007","text":"poking greader trends - some must live, some must die","id":89591602},"profile_image_url":"http://assets1.twitter.com/images/default_image.gif?1180755379","screen_name":"robertbrook"},{"name":"cote","description":"Industry analyst with RedMonk. DrunkAndRetired.com. YUH!","location":"Austin, Texas","url":"http://www.peopleoverprocess.com/","id":53953,"protected":false,"status":{"created_at":"Sun Jun 03 19:54:55 +0000 2007","text":"Flight to ORD delayed 2 hours. This can't be good.","id":89589762},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/53953/normal/square-lil-hat.jpg?1171962174","screen_name":"cote"},{"name":"Moby","description":"I'm here. Now what?","location":"San Francisco","url":"http://blog.mobius.name","id":4296211,"protected":false,"status":{"created_at":"Sun Jun 03 19:46:00 +0000 2007","text":"Responding to hate mail. \"Ignunce\" at its finest","id":89583032},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/4296211/normal/mobycycle.031007.jpg?1176350283","screen_name":"ibod8x5"},{"name":"Alex King","description":"","location":"Denver, CO","url":"http://alexking.org","id":101143,"protected":false,"status":{"created_at":"Sun Jun 03 19:06:54 +0000 2007","text":"@andrewhyde Coverage was poor enough in the area to drive me back to Sprint.","id":89554432},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/101143/normal/alex_king.jpg?1171953145","screen_name":"alexkingorg"},{"name":"Patrick Mueller","description":"Graying IBMer working for the WebSphere organization","location":"The Triangle, NC","url":"http://muellerware.org","id":765080,"protected":false,"status":{"created_at":"Sun Jun 03 18:48:51 +0000 2007","text":"@JoshStaiger that was one I was looking at, which Google (coincidentally?) ranked highly. Wondering how far I can get w/avacado & salsa","id":89537732},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/765080/normal/glasses-down-115x115-gradient.jpg?1177726044","screen_name":"pmuellr"},{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"status":{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","screen_name":"kesuke"},{"name":"Jeff Barr","description":"Amazon Web Services Evangelist, Blogger, Father of 5.","location":"Sammamish, Washington, USA","url":"http://www.jeff-barr.com","id":48443,"protected":false,"status":{"created_at":"Sun Jun 03 16:43:47 +0000 2007","text":"Preparing for trip to DC tomorrow AM - lots of reading materials, TODO list, iPod fresh, camera charged, laptop packed. Arrange for taxi.","id":89432112},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/48443/normal/jeff_barr.jpg?1171961668","screen_name":"jeffbarr"},{"name":"Paul Downey","description":"Computing Industry Bi-product","location":"Berkhamsted, UK","url":"http://blog.whatfettle.com","id":13486,"protected":false,"status":{"created_at":"Sun Jun 03 16:24:53 +0000 2007","text":"back from tea and cake at the village hall now resuming futile search for a family holiday on 'tinternet","id":89413652},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/13486/normal/psd-75x75.jpg?1171954493","screen_name":"psd"},{"name":"C Ortiz","description":"Amongst other things that keep me far away from a computer, I manage PrivateMilitary.org","location":"UK | US","url":"http://www.privatemilitary.org/","id":2306071,"protected":false,"status":{"created_at":"Sun Jun 03 16:10:44 +0000 2007","text":"Sunday reading: security contractors snatched without a shot: http://tinyurl.com/2qyv6x","id":89399902},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/2306071/normal/photo_twitter.jpg?1177775770","screen_name":"privatemilitary"},{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"status":{"created_at":"Sun Jun 03 15:57:16 +0000 2007","text":"ah, it's under \"Reply\"","id":89385112},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"},{"name":"Alexander J Turner","description":"Hubby, Dad & Chemist - Escaped Into IT Land To Cause Damage As A Software Architect!","location":"Oxford, UK","url":"http://www.nerds-central.com","id":1621891,"protected":false,"status":{"created_at":"Sun Jun 03 15:34:24 +0000 2007","text":"@chrisborgan - Just got back from a 20mile bike ride. You're right - it is all about habits. Thanks! less than 5 seconds ago from web","id":89364302},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/1621891/normal/MadAlex256.png?1178292687","screen_name":"AlexTurner"},{"name":"Josh Lucas","description":"Just adding another bit of distraction...","location":"Pasadena, CA","url":"http://www.stonecottage.com/josh/","id":47023,"protected":false,"status":{"created_at":"Sun Jun 03 15:16:47 +0000 2007","text":"wearing my new old-skool upcoming shirt","id":89345632},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/47023/normal/cubs_me.jpg?1171961535","screen_name":"lucasjosh"},{"name":"jark","description":"Co-Founder of deviantART","location":"Tokyo, Japan","url":"http://jarkolicious.com/","id":39653,"protected":false,"status":{"created_at":"Sun Jun 03 14:51:25 +0000 2007","text":"heads to bed, but first starts a process to burn 300 to DVD.","id":89318752},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/39653/normal/jark-static.jpg?1171960858","screen_name":"jark"},{"name":"Paul Terry Walhus","description":"developer building team for http://searchslides.com & http://web2.0slides.com","location":"Austin, Texas","url":"http://austinblogger.com/blog/","id":1418,"protected":false,"status":{"created_at":"Sun Jun 03 14:05:39 +0000 2007","text":"Geni - Everyone's Related: cool web 2.0 family tree app, requires login http://www.geni.com/tree","id":89277722},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/1418/normal/paulterrywalhus.jpg?1175716284","screen_name":"springnet"},{"name":"Floozle","description":"","location":"","url":null,"id":58863,"protected":false,"status":{"created_at":"Sun Jun 03 12:50:02 +0000 2007","text":"Off to the office to complete the conversion","id":89202702},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/58863/normal/29logo3-med.gif?1174609772","screen_name":"Floozle"},{"name":" T\u00c7","description":"Chem: Physics: Math: Logic: Observation: Analysis: Hypothesis: Experimentation: Iteration: Evolution: Science. It works, bitches.","location":"San Francisco, CA","url":"http://tantek.com/","id":11628,"protected":false,"status":{"created_at":"Sun Jun 03 10:34:10 +0000 2007","text":"pondering how 1 can b so tired & sore after exercise, yet so energized as 2 b up at 3:30am, w only *1* coffee instead of usual 3 per workday","id":89104612},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/11628/normal/icon200px.jpg?1171953743","screen_name":"t"},{"name":"adam","description":"http://ifindkarma.com/","location":"Palo Alto, CA","url":"http://renkoo.com/profile/ee0e95249268b86ff2053bef214bfeda","id":1688,"protected":false,"status":{"created_at":"Sun Jun 03 07:07:00 +0000 2007","text":"Norman Mailer on the idea of perfect happiness: \"A fool draws a road map to his magic city.\" (Vanity Fair, Jan 2007)","id":88952182},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/1688/normal/RifkinIcon.jpg?1179096764","screen_name":"ifindkarma"},{"name":"Cameron Walters","description":"getting satisfaction daily","location":"San Francisco","url":"http://chuddup.com/","id":3922,"protected":false,"status":{"created_at":"Sun Jun 03 06:54:14 +0000 2007","text":"Earlier: Pilates. Now: Spazzy thrashcore electro.","id":88940252},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/3922/normal/awesome.jpg?1177104664","screen_name":"ceedub"},{"name":"Jason Calacanis","description":"Looking for TNBT","location":"LA","url":"http://www.calacanis.com","id":3840,"protected":false,"status":{"created_at":"Sun Jun 03 06:49:36 +0000 2007","text":"Having dinner with loic, sam harris, jared diamond, and john brockman... Among others.","id":88935882},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3840/normal/jason2.jpg?1172771113","screen_name":"JasonCalacanis"},{"name":"hober","description":"One-line bios are hard.","location":"San Diego, CA","url":"http://edward.oconnor.cx/","id":13607,"protected":false,"status":{"created_at":"Sun Jun 03 06:41:04 +0000 2007","text":"Emacs 22 released while I was at BarCamp. heh. http://www.gnu.org/software/emacs/","id":88929942},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/13607/normal/ted-icon-large.jpg?1171954556","screen_name":"hober"},{"name":" Jay","description":"Senior Systems Administrator of the Unix variety ","location":"Houston","url":null,"id":336113,"protected":false,"status":{"created_at":"Sun Jun 03 04:36:50 +0000 2007","text":"Wonders what kind of cheese Kristie spent $70 on.","id":88843542},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/336113/normal/MyPicture.jpg?1179799311","screen_name":"meangrape"},{"name":"Tatsuhiko Miyagawa","description":"Yet another Perl hacker","location":"San Francisco","url":"http://bulknews.vox.com/","id":731253,"protected":false,"status":{"created_at":"Sun Jun 03 04:06:57 +0000 2007","text":"had a good rice/veggie/pork noodle and shrimp fried rice in HoH. Feeling almost full and too ricey","id":88820042},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/731253/normal/P506iC0003735833.jpg?1170146286","screen_name":"miyagawa"},{"name":" Rod Begbie","description":"Cantankerous Scots git.","location":"Somerville, MA","url":"http://groovymother.com/","id":761,"protected":false,"status":{"created_at":"Sun Jun 03 03:43:42 +0000 2007","text":"Fun first day in LA. Looked at stars in sidewalk, got approached by Scientologists three times, Mexican dinner with old friends. Happy!","id":88800482},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/761/normal/vatar.png?1167697094","screen_name":"rodbegbie"},{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http://www.veronicabelmont.com","id":10350,"protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/10350/normal/mypictr_140x144.jpg?1179253942","screen_name":"Veronica"},{"name":"eric L","description":"mobile expert, internet idiot","location":"san francisco","url":"http://www.n1s.net","id":8291,"protected":false,"status":{"created_at":"Sun Jun 03 03:08:04 +0000 2007","text":"A howat once said there's no scorin with the sporin.","id":88771502},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/8291/normal/bandit.jpg?1165079078","screen_name":"n1s"},{"name":"Mr Messina","description":"As if concentrating wasn't hard enough already.","location":"94107","url":"http://factoryjoe.com/","id":1186,"protected":false,"status":{"created_at":"Sun Jun 03 01:42:47 +0000 2007","text":"OpenID won the disruptor award at The NextWeb conference! http://tinyurl.com/yq8e89","id":88706172},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/1186/normal/devil_150.jpg?1171953828","screen_name":"factoryjoe"},{"name":"Scobleizer","description":"Tech geek blogger @ http://scobleizer.com","location":"Half Moon Bay, California, USA","url":"http://scobleshow.com","id":13348,"protected":false,"status":{"created_at":"Sun Jun 03 01:06:52 +0000 2007","text":"I can't get into my Flickr account. I can get into Yahoo, but not Flickr... http://tinyurl.com/2b63rh","id":88677782},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/13348/normal/trafficlight.jpg?1175390038","screen_name":"Scobleizer"},{"name":"Andy Edmonds","description":null,"location":null,"url":null,"id":936361,"protected":false,"status":{"created_at":"Sat Jun 02 22:09:55 +0000 2007","text":"Tries out squidoo at http://www.squidoo.com/eyetrack","id":88551872},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/936361/normal/andy_headshot_48x48.png?1176350570","screen_name":"andyed"},{"name":"Ian McKellar","description":"","location":"San Francisco, CA","url":"http://ian.mckellar.org/","id":259,"protected":false,"status":{"created_at":"Sat Jun 02 20:55:30 +0000 2007","text":"lolfeeds got shut down for using too much cpu so I had to get around to adding a caching layer. it fixed some character encoding issues too!","id":88496342},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/259/normal/trapped.jpg?1171957894","screen_name":"ianmckellar"},{"name":"Jeremy Zawodny","description":"I fly and geek.","location":"San Jose, CA","url":"http://jeremy.zawodny.com/blog/","id":97933,"protected":false,"status":{"created_at":"Sat Jun 02 20:04:53 +0000 2007","text":"errands and packing for a week in the desert next week...","id":88458352},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/97933/normal/Zawodny-md.jpg?1166680073","screen_name":"jzawodn"},{"name":"Mihai","description":"","location":"New York, NY","url":"http://persistent.info/","id":28203,"protected":false,"status":{"created_at":"Sat Jun 02 19:46:47 +0000 2007","text":"Back on campus for reunions. Don't feel old just yet. Phew.","id":88444532},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/28203/normal/mihaip.jpg?1171958069","screen_name":"mihai"},{"name":"Robert Merrill","description":"Helping People & Teams Become Better","location":"Provo, Utah","url":"http://www.utahtechjobs.com","id":755721,"protected":false,"status":{"created_at":"Sat Jun 02 17:16:45 +0000 2007","text":"Heading home","id":88316782},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/755721/normal/50998991_N00.jpg?1170908379","screen_name":"robertmerrill"},{"name":"Darren Kulp","description":"Computer, linguistics, and music geek. What else is there to say?","location":"Eau Claire, WI, USA","url":"http://kulp.ch/","id":6083072,"protected":false,"status":{"created_at":"Sat Jun 02 15:39:55 +0000 2007","text":"twitterim finally works? Fancy that.","id":88225602},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/6083072/normal/f1.jpg?1179303021","screen_name":"kulp"},{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","url":"http://www.seancoon.org","id":677903,"protected":false,"status":{"created_at":"Sat Jun 02 08:27:03 +0000 2007","text":"Holy moly. Time to go fishing. Yeeeaaahhh!","id":87876222},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/677903/normal/sean-85.png?1173252480","screen_name":"spcoon"},{"name":"Niall","description":"Squeezing the most out of everything but my phone","location":"San Francisco, CA","url":"http://www.niallkennedy.com/","id":1085,"protected":true,"status":{"created_at":"Sat Jun 02 04:32:57 +0000 2007","text":"your server migration is at 11. oh, we changed our minds, we're doing it at 9:30. ummm....yay?","id":87696902},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/1085/normal/niall_ringer.jpg?1171953434","screen_name":"niall"},{"name":"George P","description":"i am illicium","location":"Bay Area","url":null,"id":796724,"protected":true,"status":{"created_at":"Sat Jun 02 04:07:24 +0000 2007","text":"Hello, Twitter! Long time no see.","id":87673952},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/796724/normal/star.gif?1172880544","screen_name":"illicium"},{"name":"Daniel E. Renfer","description":"Freelance Individual","location":"Ypsilanti, MI","url":"http://kronkltd.net/","id":11491,"protected":false,"status":{"created_at":"Fri Jun 01 21:15:50 +0000 2007","text":"In the immortal words of Doug from MTV's The State: \"I'm outta heere\"","id":87306192},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/11491/normal/886.jpg?1171953691","screen_name":"duck1123"},{"name":"Simon Willison","description":null,"location":"London","url":"http://simonwillison.net/","id":12497,"protected":false,"status":{"created_at":"Fri Jun 01 21:01:59 +0000 2007","text":"Nat and I are in Brighton this weekend, anyone want to meet up?","id":87291372},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/12497/normal/298777290_a5ed9a4e70_m.jpg?1171954113","screen_name":"simonw"},{"name":"Yoz","description":"A small yoz-type object, currently residing in San Francisco","location":"San Francisco, CA","url":"http://yoz.com/","id":12329,"protected":false,"status":{"created_at":"Fri Jun 01 18:13:23 +0000 2007","text":"@riffraff814: Thanks but no thanks, don't drink coffee, my body is a temple filled with magical prancing ponies yay peanut M&M overdose","id":87132942},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/12329/normal/yozlap-100.jpg?1171954052","screen_name":"yoz"},{"name":"J Scud","description":"Software can be beautiful.","location":"Silicon Valley California","url":"http://jeffreyscudder.blogspot.com/","id":1359571,"protected":false,"status":{"created_at":"Fri Jun 01 07:14:58 +0000 2007","text":"Watching videos from Google Developer Day: http://tinyurl.com/ywr8cz","id":86425072},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/1359571/normal/temp_img.jpg?1177211826","screen_name":"jaguarjaws"},{"name":"Joe Duck","description":"Travel Internet Oregon Guy","location":"Oregon","url":"http://joeduck.wordpress.com","id":150433,"protected":false,"status":{"created_at":"Fri Jun 01 07:09:02 +0000 2007","text":"Removed Google downranking on an obscure travel page. Next test - non-obscure page fixes.http://tinyurl.com/227re8","id":86418302},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/150433/normal/joebiopic.jpg?1173598016","screen_name":"joeduck"},{"name":"Jon Phillips","description":"http://rejon.org/bio/","location":"SF","url":"http://rejon.org","id":744063,"protected":false,"status":{"created_at":"Fri Jun 01 04:59:45 +0000 2007","text":"Killing it...","id":86303992},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/744063/normal/jon_by_ryan_junell_fixed.png?1170353692","screen_name":"rejon"},{"name":"The BFF","description":"","location":"Netherlands","url":"http://doncrowley.blogspot.com/","id":1620121,"protected":false,"status":{"created_at":"Thu May 31 21:30:08 +0000 2007","text":"Twitteriffic addicts - some power tips: I am amazed at how many still do not know these tricks with twitte.. http://tinyurl.com/2mzjod","id":85901962},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/1620121/normal/bff.jpg?1174419110","screen_name":"TheBFF"},{"name":"Andy Armstrong","description":"Perl Bloke","location":"Cumbria, UK","url":"http://hexten.net","id":1022831,"protected":false,"status":{"created_at":"Thu May 31 18:58:03 +0000 2007","text":"37 downloads pending to iTMS. Keeps timing out. Bah. Anyone else?","id":85769802},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/1022831/normal/moi.jpg?1176741181","screen_name":"AndyArmstrong"},{"name":"Brian Suda","description":"SWM Informatician @64.132511;-21.906494 (microformats,GRDDL,XSLT,PHP,picoformats,XHTML)","location":"Iceland","url":"http://suda.co.uk/","id":15313,"protected":false,"status":{"created_at":"Thu May 31 17:54:18 +0000 2007","text":"is happy that tonight is the last night that you can smoke in bars and restaurants in Iceland. Washing machines won't be happy with this.","id":85708142},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/15313/normal/gravatar.png?1175011623","screen_name":"briansuda"},{"name":"arunaurl","description":"Make long URLs small!","location":"All over the web","url":"http://arunaurl.com","id":6371812,"protected":false,"status":{"created_at":"Tue May 29 23:49:15 +0000 2007","text":"Aruna URL now available as a Zimbra Collaboration Suite plug-in / zimlet http://arunaurl.com/b25","id":83248192},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/6371812/normal/Aruna_URL_logo.png?1180314185","screen_name":"arunaurl"},{"name":"Hiroshi Ayukawa","description":"A Japanese ordinary Python & C++ programmer","location":"Tokyo, Japan","url":null,"id":3786561,"protected":false,"status":{"created_at":"Tue May 29 16:22:31 +0000 2007","text":"I don't like to use JAVA any more... Let me sleep, Ahhhhh! Give me Python.","id":82802982},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3786561/normal/muku.jpg?1176037335","screen_name":"hiroshiykw"},{"name":" Scriptless Day","description":"","location":"","url":"http://www.scriptlessday.com","id":3641801,"protected":false,"status":{"created_at":"Mon May 28 07:55:27 +0000 2007","text":"39 Days Left till Scriptless Day!! http://www.scriptlessday.com :D","id":81305782},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/3641801/normal/splash2.png?1175927287","screen_name":"scriptlessday"},{"name":"brady forrest","description":null,"location":null,"url":null,"id":6140,"protected":false,"status":{"created_at":"Sat May 26 02:51:53 +0000 2007","text":"im at convergence 13 in pdx. looking for events inspiration.","id":78751852},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/6140/normal/IMG_9629.jpg?1175295805","screen_name":"brady"},{"name":"TV with MeeVee","description":"We heart TV","location":"Burlingame, CA","url":"http://blog.meevee.com","id":2371741,"protected":false,"status":{"created_at":"Wed May 23 17:22:20 +0000 2007","text":"Nicole Richie Denies Rehab Rumors http://tinyurl.com/2hvn8h","id":75514922},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/2371741/normal/meevee_potato_ad3SoSF.jpg?1174950717","screen_name":"TVwithMeeVee"},{"name":"Emmet Connolly","description":"I have nothing to say (and I am saying it)","location":"Dublin","url":"http://blog.thoughtwax.com/","id":11323,"protected":false,"status":{"created_at":"Tue May 22 21:34:28 +0000 2007","text":"Virtual subdomains + .htaccess = ow, my brain. Anyway, three years late but my blog now has pretty urls. Hooray!","id":74472602},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/11323/normal/me.jpg?1171953619","screen_name":"thoughtwax"},{"name":"Bilal Hameed","description":"Editor of Startup Meme","location":"As if it matters","url":"http://startupmeme.com","id":5629992,"protected":false,"status":{"created_at":"Tue May 15 16:41:05 +0000 2007","text":"http://tinyurl.com/2h474q Use EasyPost To Send Snail Mail in Canada","id":65079402},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/5629992/normal/me.jpg?1177848235","screen_name":"startupmeme"},{"name":"Koen Sadza","description":"I study Applied Physics. My hobbies are my GF,SInging,Scouting,Clubbing,Computers","location":"Eindhoven, The Netherlands","url":"http://weblog.ksdz.nl/","id":803238,"protected":false,"status":{"created_at":"Sat May 12 14:16:37 +0000 2007","text":"Net terug van Scouting","id":61483292},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/803238/normal/kerst.jpg?1172746148","screen_name":"ksdz"},{"name":"Webtickle","description":"","location":"California","url":null,"id":1322691,"protected":false,"status":{"created_at":"Thu May 10 03:25:15 +0000 2007","text":"The Freelancer\u2019s Toolset: 100 Web Apps for Everything You Will Possibly Need\" http://tinyurl.com/2dztm8","id":58099692},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/1322691/normal/large4197.png?1174089345","screen_name":"Webtickle"},{"name":"Yes","description":null,"location":null,"url":null,"id":765884,"protected":false,"status":{"created_at":"Wed May 09 20:43:39 +0000 2007","text":"Whoo hoo! Yay!","id":57715832},"profile_image_url":"http://assets1.twitter.com/images/default_image.gif?1180755379","screen_name":"Yes"},{"name":"Leland Harding III","description":"Traditional country artist, from South Dakota with a major background in the country music scene","location":"South Dakota","url":"http://www.lelandharding.com","id":3545551,"protected":false,"status":{"created_at":"Tue May 08 08:01:31 +0000 2007","text":"Anyone who has been scammed by these people can get ahold of me at leland@ldharding.com","id":54614912},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/3545551/normal/image01-xml.jpg?1175797244","screen_name":"ldcountry"},{"name":"Trendio M","description":"","location":"","url":"http://www.trendio.com","id":5845532,"protected":false,"status":{"created_at":"Mon May 07 22:32:39 +0000 2007","text":"Open Source and Linux are up today. Which will be this week's top technology trends? http://tinyurl.com/2bpknb","id":53868212},"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/5845532/normal/logo48x48.gif?1178576662","screen_name":"Trendio_M"},{"name":"Patrick Wang","description":"","location":"San Francisco, CA","url":"http://junesix.org","id":799123,"protected":false,"status":{"created_at":"Mon Apr 16 21:57:48 +0000 2007","text":"@jabancroft optimized builds at beatnikpad.com","id":30322371},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/799123/normal/samurai_head.gif?1173313806","screen_name":"junesix"},{"name":"Dion Almaer","description":"ajaxian, googley, and techno","location":"Palo Alto, CA","url":"http://almaer.com/blog","id":4216361,"protected":false,"status":{"created_at":"Wed Apr 11 17:48:16 +0000 2007","text":"Enjoying being able to talk about the Google Developer Day!","id":24861261},"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/4216361/normal/logo-48x48.jpg?1176313520","screen_name":"dalmaer"},{"name":"Chris DiBona","description":null,"location":null,"url":null,"id":44423,"protected":true,"status":{"created_at":"Fri Mar 16 16:42:55 +0000 2007","text":"Cripes, trapped on the tarmac at iad. Today, we sit in hell:","id":8638201},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/44423/normal/103ID_DiBona.jpg?1171961329","screen_name":"cdibona"},{"name":"napoleone","description":"","location":"piacenza ITALY","url":null,"id":740133,"protected":false,"status":{"created_at":"Sat Feb 17 14:36:53 +0000 2007","text":"sono indeciso tra casual o un p\u00f2 sportive!!!","id":5555694},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/740133/normal/P6050088.jpg?1170800580","screen_name":"Cris"},{"name":"Brian Tucker","description":null,"location":null,"url":null,"id":759328,"protected":false,"status":{"created_at":"Sun Feb 11 04:11:11 +0000 2007","text":"Chinese new year starts next weekend I think.","id":5422032},"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/759328/normal/bt.png?1170987088","screen_name":"brian318"},{"name":"Josh H","description":null,"location":null,"url":null,"id":2831771,"protected":false,"profile_image_url":"http://assets1.twitter.com/images/default_image.gif?1180755379","screen_name":"josh59x"},{"name":"mfagan","description":"","location":"Canada","url":"http://faganm.com/","id":677403,"protected":false,"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/677403/normal/me_with_hat.jpg?1171966071","screen_name":"mfagan"}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/friendship-create.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/friendship-create.json --- python-twitter-0.5/testdata/friendship-create.json 2007-07-15 07:10:13.000000000 +0100 +++ python-twitter-0.6/testdata/friendship-create.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"} +{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"} diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/friendship-destroy.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/friendship-destroy.json --- python-twitter-0.5/testdata/friendship-destroy.json 2007-07-15 07:10:13.000000000 +0100 +++ python-twitter-0.6/testdata/friendship-destroy.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"} +{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"} diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/friends.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/friends.json --- python-twitter-0.5/testdata/friends.json 2007-06-03 21:00:45.000000000 +0100 +++ python-twitter-0.6/testdata/friends.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"name":" T\u00c7","description":"Chem: Physics: Math: Logic: Observation: Analysis: Hypothesis: Experimentation: Iteration: Evolution: Science. It works, bitches.","location":"San Francisco, CA","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/11628\/normal\/icon200px.jpg?1171953743","url":"http:\/\/tantek.com\/","id":11628,"screen_name":"t","protected":false,"status":{"created_at":"Sun Jun 03 10:34:10 +0000 2007","text":"pondering how 1 can b so tired & sore after exercise, yet so energized as 2 b up at 3:30am, w only *1* coffee instead of usual 3 per workday","id":89104612}},{"name":"adam","description":"http:\/\/ifindkarma.com\/","location":"Palo Alto, CA","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/1688\/normal\/RifkinIcon.jpg?1179096764","url":"http:\/\/renkoo.com\/profile\/ee0e95249268b86ff2053bef214bfeda","id":1688,"screen_name":"ifindkarma","protected":false,"status":{"created_at":"Sun Jun 03 07:07:00 +0000 2007","text":"Norman Mailer on the idea of perfect happiness: \"A fool draws a road map to his magic city.\" (Vanity Fair, Jan 2007)","id":88952182}},{"name":"Alex King","description":"","location":"Denver, CO","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/101143\/normal\/alex_king.jpg?1171953145","url":"http:\/\/alexking.org","id":101143,"screen_name":"alexkingorg","protected":false,"status":{"created_at":"Sun Jun 03 19:06:54 +0000 2007","text":"@andrewhyde Coverage was poor enough in the area to drive me back to Sprint.","id":89554432}},{"name":"Andy Edmonds","description":null,"location":null,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/936361\/normal\/andy_headshot_48x48.png?1176350570","url":null,"id":936361,"screen_name":"andyed","protected":false,"status":{"created_at":"Sat Jun 02 22:09:55 +0000 2007","text":"Tries out squidoo at http:\/\/www.squidoo.com\/eyetrack","id":88551872}},{"name":"anildash","description":"That blogging guy.","location":"New York, New York","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/36823\/normal\/6315878.jpg?1171960613","url":"http:\/\/www.anildash.com\/","id":36823,"screen_name":"anildash","protected":false,"status":{"created_at":"Fri Jun 01 20:33:42 +0000 2007","text":"I AM BURNING MY FEED IN PROTEST OF THOSE BASTARDS SELLING OUT. WHO'S WITH ME?!","id":87263942}},{"name":"Biz Stone","description":"I work here!","location":"Berkeley, CA","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/13\/normal\/biz_toon.png?1171954299","url":"http:\/\/bizstone.com","id":13,"screen_name":"biz","protected":false,"status":{"created_at":"Sun Jun 03 02:36:51 +0000 2007","text":"Gmail down to 8 while Livy makes curried carrot soup and of course Star Trek Voyager is on marathon mode","id":88746562}},{"name":"brady forrest","description":null,"location":null,"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/6140\/normal\/IMG_9629.jpg?1175295805","url":null,"id":6140,"screen_name":"brady","protected":false,"status":{"created_at":"Sat May 26 02:51:53 +0000 2007","text":"im at convergence 13 in pdx. looking for events inspiration.","id":78751852}},{"name":"Brian Suda","description":"SWM Informatician @64.132511;-21.906494 (microformats,GRDDL,XSLT,PHP,picoformats,XHTML)","location":"Iceland","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/15313\/normal\/gravatar.png?1175011623","url":"http:\/\/suda.co.uk\/","id":15313,"screen_name":"briansuda","protected":false,"status":{"created_at":"Thu May 31 17:54:18 +0000 2007","text":"is happy that tonight is the last night that you can smoke in bars and restaurants in Iceland. Washing machines won't be happy with this.","id":85708142}},{"name":"Buzz Andersen","description":null,"location":"San Francisco, CA","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/528\/normal\/buzz-jacket-tiny.jpg?1171962059","url":"http:\/\/buzz.vox.com","id":528,"screen_name":"buzz","protected":false,"status":{"created_at":"Sun Jun 03 18:56:37 +0000 2007","text":"At Madison Square Park, waiting to meet my Shake Shack compatriots.","id":89543882}},{"name":"Case","description":null,"location":"San Francisco, CA, USA","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/409\/normal\/me-skype.jpg?1177561455","url":"http:\/\/vedana.net\/","id":409,"screen_name":"Case","protected":true,"status":{"created_at":"Sun Jun 03 18:25:46 +0000 2007","text":"still buzzing from the arcade fire show, listening to neon bible and dancing around in delight!","id":89518902}},{"name":"Chris DiBona","description":null,"location":null,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/44423\/normal\/103ID_DiBona.jpg?1171961329","url":null,"id":44423,"screen_name":"cdibona","protected":true,"status":{"created_at":"Fri Mar 16 16:42:55 +0000 2007","text":"Cripes, trapped on the tarmac at iad. Today, we sit in hell:","id":8638201}},{"name":"Dave McClure","description":"Master of 500 Hats","location":"silicon valley, sf bay area","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/1081\/normal\/guido3.jpg?1171953420","url":"http:\/\/500hats.typepad.com","id":1081,"screen_name":"davemc500hats","protected":false,"status":{"created_at":"Fri Jun 01 09:35:11 +0000 2007","text":"just blogged about Sacks, Facebook, Wisdom of Crowds http:\/\/tinyurl.com\/28v44h","id":86555632}},{"name":"Dion Almaer","description":"ajaxian, googley, and techno","location":"Palo Alto, CA","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/4216361\/normal\/logo-48x48.jpg?1176313520","url":"http:\/\/almaer.com\/blog","id":4216361,"screen_name":"dalmaer","protected":false,"status":{"created_at":"Wed Apr 11 17:48:16 +0000 2007","text":"Enjoying being able to talk about the Google Developer Day!","id":24861261}},{"name":"eric L","description":"mobile expert, internet idiot","location":"san francisco","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/8291\/normal\/bandit.jpg?1165079078","url":"http:\/\/www.n1s.net","id":8291,"screen_name":"n1s","protected":false,"status":{"created_at":"Sun Jun 03 03:08:04 +0000 2007","text":"A howat once said there's no scorin with the sporin.","id":88771502}},{"name":"Evan Williams","description":"Founder of Obvious ","location":"San Francisco, CA","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/20\/normal\/ev-sky.jpg?1175282926","url":"http:\/\/evhead.com","id":20,"screen_name":"ev","protected":false,"status":{"created_at":"Sun Jun 03 07:04:50 +0000 2007","text":"This bathroom has an overload of marble","id":88950312}},{"name":"Greg Stein","description":null,"location":null,"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/12449\/normal\/gstein-by-pdcawley-cropped.jpg?1171954097","url":null,"id":12449,"screen_name":"gstein","protected":false},{"name":"Ian McKellar","description":"","location":"San Francisco, CA","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/259\/normal\/trapped.jpg?1171957894","url":"http:\/\/ian.mckellar.org\/","id":259,"screen_name":"ianmckellar","protected":false,"status":{"created_at":"Sat Jun 02 20:55:30 +0000 2007","text":"lolfeeds got shut down for using too much cpu so I had to get around to adding a caching layer. it fixed some character encoding issues too!","id":88496342}},{"name":"jark","description":"Co-Founder of deviantART","location":"Tokyo, Japan","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/39653\/normal\/jark-static.jpg?1171960858","url":"http:\/\/jarkolicious.com\/","id":39653,"screen_name":"jark","protected":false,"status":{"created_at":"Sun Jun 03 14:51:25 +0000 2007","text":"heads to bed, but first starts a process to burn 300 to DVD.","id":89318752}},{"name":"Jeff Barr","description":"Amazon Web Services Evangelist, Blogger, Father of 5.","location":"Sammamish, Washington, USA","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/48443\/normal\/jeff_barr.jpg?1171961668","url":"http:\/\/www.jeff-barr.com","id":48443,"screen_name":"jeffbarr","protected":false,"status":{"created_at":"Sun Jun 03 16:43:47 +0000 2007","text":"Preparing for trip to DC tomorrow AM - lots of reading materials, TODO list, iPod fresh, camera charged, laptop packed. Arrange for taxi.","id":89432112}},{"name":"Jeremy Zawodny","description":"I fly and geek.","location":"San Jose, CA","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/97933\/normal\/Zawodny-md.jpg?1166680073","url":"http:\/\/jeremy.zawodny.com\/blog\/","id":97933,"screen_name":"jzawodn","protected":false,"status":{"created_at":"Sat Jun 02 20:04:53 +0000 2007","text":"errands and packing for a week in the desert next week...","id":88458352}},{"name":"John Gruber","description":"Raconteur.","location":"Philadelphia","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/33423\/normal\/gruber-wanamaker-monorail.jpg?1171960346","url":"http:\/\/daringfireball.net","id":33423,"screen_name":"gruber","protected":false,"status":{"created_at":"Sun Jun 03 14:51:07 +0000 2007","text":"Needless to say, it was great time.","id":89318502}},{"name":"Josh H","description":null,"location":null,"profile_image_url":"http:\/\/assets1.twitter.com\/images\/default_image.gif?1180755379","url":null,"id":2831771,"screen_name":"josh59x","protected":false},{"name":"Josh Lucas","description":"Just adding another bit of distraction...","location":"Pasadena, CA","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/47023\/normal\/cubs_me.jpg?1171961535","url":"http:\/\/www.stonecottage.com\/josh\/","id":47023,"screen_name":"lucasjosh","protected":false,"status":{"created_at":"Sun Jun 03 15:16:47 +0000 2007","text":"wearing my new old-skool upcoming shirt","id":89345632}},{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false,"status":{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102}},{"name":"Kevin Burton","description":null,"location":null,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/62763\/normal\/me-profile.jpg?1173387685","url":null,"id":62763,"screen_name":"burtonator","protected":false,"status":{"created_at":"Sun Jun 03 08:43:41 +0000 2007","text":"zoe is playing with a plastic ring from a milk bottle.... cheap toy!","id":89021982}},{"name":"mfagan","description":"","location":"Canada","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/677403\/normal\/me_with_hat.jpg?1171966071","url":"http:\/\/faganm.com\/","id":677403,"screen_name":"mfagan","protected":false},{"name":"Mihai","description":"","location":"New York, NY","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/28203\/normal\/mihaip.jpg?1171958069","url":"http:\/\/persistent.info\/","id":28203,"screen_name":"mihai","protected":false,"status":{"created_at":"Sat Jun 02 19:46:47 +0000 2007","text":"Back on campus for reunions. Don't feel old just yet. Phew.","id":88444532}},{"name":"Mr Messina","description":"As if concentrating wasn't hard enough already.","location":"94107","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/1186\/normal\/devil_150.jpg?1171953828","url":"http:\/\/factoryjoe.com\/","id":1186,"screen_name":"factoryjoe","protected":false,"status":{"created_at":"Sun Jun 03 01:42:47 +0000 2007","text":"OpenID won the disruptor award at The NextWeb conference! http:\/\/tinyurl.com\/yq8e89","id":88706172}},{"name":"Niall","description":"Squeezing the most out of everything but my phone","location":"San Francisco, CA","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/1085\/normal\/niall_ringer.jpg?1171953434","url":"http:\/\/www.niallkennedy.com\/","id":1085,"screen_name":"niall","protected":true,"status":{"created_at":"Sat Jun 02 04:32:57 +0000 2007","text":"your server migration is at 11. oh, we changed our minds, we're doing it at 9:30. ummm....yay?","id":87696902}},{"name":"Nick Douglas","description":"I am clever. Are you clever too?","location":"San Francisco","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/1084\/normal\/Nick-tiny-face.jpg?1171953430","url":"http:\/\/lookshiny.com","id":1084,"screen_name":"nick","protected":false,"status":{"created_at":"Sun Jun 03 07:52:33 +0000 2007","text":"Even Mark Day was at Arcade Fire, and I conned myself out of going with a cute friend 'cuz I'd told her how much I hate the band.","id":88984072}},{"name":"Paul Downey","description":"Computing Industry Bi-product","location":"Berkhamsted, UK","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/13486\/normal\/psd-75x75.jpg?1171954493","url":"http:\/\/blog.whatfettle.com","id":13486,"screen_name":"psd","protected":false,"status":{"created_at":"Sun Jun 03 16:24:53 +0000 2007","text":"back from tea and cake at the village hall now resuming futile search for a family holiday on 'tinternet","id":89413652}},{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/677903\/normal\/sean-85.png?1173252480","url":"http:\/\/www.seancoon.org","id":677903,"screen_name":"spcoon","protected":false,"status":{"created_at":"Sat Jun 02 08:27:03 +0000 2007","text":"Holy moly. Time to go fishing. Yeeeaaahhh!","id":87876222}},{"name":"Simon Willison","description":null,"location":"London","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/12497\/normal\/298777290_a5ed9a4e70_m.jpg?1171954113","url":"http:\/\/simonwillison.net\/","id":12497,"screen_name":"simonw","protected":false,"status":{"created_at":"Fri Jun 01 21:01:59 +0000 2007","text":"Nat and I are in Brighton this weekend, anyone want to meet up?","id":87291372}},{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","url":"http:\/\/redmonk.com\/sogrady","id":143883,"screen_name":"sogrady","protected":false,"status":{"created_at":"Sun Jun 03 15:57:16 +0000 2007","text":"ah, it's under \"Reply\"","id":89385112}},{"name":"Tatsuhiko Miyagawa","description":"Yet another Perl hacker","location":"San Francisco","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/731253\/normal\/P506iC0003735833.jpg?1170146286","url":"http:\/\/bulknews.vox.com\/","id":731253,"screen_name":"miyagawa","protected":false,"status":{"created_at":"Sun Jun 03 04:06:57 +0000 2007","text":"had a good rice\/veggie\/pork noodle and shrimp fried rice in HoH. Feeling almost full and too ricey","id":88820042}},{"name":"Tom Coates","description":"Scruffy, grumpy, social mediaesque...","location":"London","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/12514\/normal\/12037949715_N01.jpg?1178111032","url":"http:\/\/www.plasticbag.org\/","id":12514,"screen_name":"plasticbagUK","protected":false,"status":{"created_at":"Sun Jun 03 18:21:20 +0000 2007","text":"My heart sinks as I pass Hackney Down.","id":89515562}},{"name":"veen","description":"I used to make small things. Now I make big things.","location":"San Francisco","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/414\/normal\/Photo_21.jpg?1171961099","url":"http:\/\/veen.com\/jeff\/","id":414,"screen_name":"veen","protected":false,"status":{"created_at":"Fri Jun 01 22:38:36 +0000 2007","text":"Received an absolutely beautiful wedding invitation, made even better by an Obi Wan Kenobi postage stamp.","id":87377882}},{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/10350\/normal\/mypictr_140x144.jpg?1179253942","url":"http:\/\/www.veronicabelmont.com","id":10350,"screen_name":"Veronica","protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212}},{"name":"Yes","description":null,"location":null,"profile_image_url":"http:\/\/assets1.twitter.com\/images\/default_image.gif?1180755379","url":null,"id":765884,"screen_name":"Yes","protected":false,"status":{"created_at":"Wed May 09 20:43:39 +0000 2007","text":"Whoo hoo! Yay!","id":57715832}},{"name":"Yoz","description":"A small yoz-type object, currently residing in San Francisco","location":"San Francisco, CA","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/12329\/normal\/yozlap-100.jpg?1171954052","url":"http:\/\/yoz.com\/","id":12329,"screen_name":"yoz","protected":false,"status":{"created_at":"Fri Jun 01 18:13:23 +0000 2007","text":"@riffraff814: Thanks but no thanks, don't drink coffee, my body is a temple filled with magical prancing ponies yay peanut M&M overdose","id":87132942}}] \ No newline at end of file +[{"name":" T\u00c7","description":"Chem: Physics: Math: Logic: Observation: Analysis: Hypothesis: Experimentation: Iteration: Evolution: Science. It works, bitches.","location":"San Francisco, CA","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/11628/normal/icon200px.jpg?1171953743","url":"http://tantek.com/","id":11628,"screen_name":"t","protected":false,"status":{"created_at":"Sun Jun 03 10:34:10 +0000 2007","text":"pondering how 1 can b so tired & sore after exercise, yet so energized as 2 b up at 3:30am, w only *1* coffee instead of usual 3 per workday","id":89104612}},{"name":"adam","description":"http://ifindkarma.com/","location":"Palo Alto, CA","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/1688/normal/RifkinIcon.jpg?1179096764","url":"http://renkoo.com/profile/ee0e95249268b86ff2053bef214bfeda","id":1688,"screen_name":"ifindkarma","protected":false,"status":{"created_at":"Sun Jun 03 07:07:00 +0000 2007","text":"Norman Mailer on the idea of perfect happiness: \"A fool draws a road map to his magic city.\" (Vanity Fair, Jan 2007)","id":88952182}},{"name":"Alex King","description":"","location":"Denver, CO","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/101143/normal/alex_king.jpg?1171953145","url":"http://alexking.org","id":101143,"screen_name":"alexkingorg","protected":false,"status":{"created_at":"Sun Jun 03 19:06:54 +0000 2007","text":"@andrewhyde Coverage was poor enough in the area to drive me back to Sprint.","id":89554432}},{"name":"Andy Edmonds","description":null,"location":null,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/936361/normal/andy_headshot_48x48.png?1176350570","url":null,"id":936361,"screen_name":"andyed","protected":false,"status":{"created_at":"Sat Jun 02 22:09:55 +0000 2007","text":"Tries out squidoo at http://www.squidoo.com/eyetrack","id":88551872}},{"name":"anildash","description":"That blogging guy.","location":"New York, New York","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/36823/normal/6315878.jpg?1171960613","url":"http://www.anildash.com/","id":36823,"screen_name":"anildash","protected":false,"status":{"created_at":"Fri Jun 01 20:33:42 +0000 2007","text":"I AM BURNING MY FEED IN PROTEST OF THOSE BASTARDS SELLING OUT. WHO'S WITH ME?!","id":87263942}},{"name":"Biz Stone","description":"I work here!","location":"Berkeley, CA","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/13/normal/biz_toon.png?1171954299","url":"http://bizstone.com","id":13,"screen_name":"biz","protected":false,"status":{"created_at":"Sun Jun 03 02:36:51 +0000 2007","text":"Gmail down to 8 while Livy makes curried carrot soup and of course Star Trek Voyager is on marathon mode","id":88746562}},{"name":"brady forrest","description":null,"location":null,"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/6140/normal/IMG_9629.jpg?1175295805","url":null,"id":6140,"screen_name":"brady","protected":false,"status":{"created_at":"Sat May 26 02:51:53 +0000 2007","text":"im at convergence 13 in pdx. looking for events inspiration.","id":78751852}},{"name":"Brian Suda","description":"SWM Informatician @64.132511;-21.906494 (microformats,GRDDL,XSLT,PHP,picoformats,XHTML)","location":"Iceland","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/15313/normal/gravatar.png?1175011623","url":"http://suda.co.uk/","id":15313,"screen_name":"briansuda","protected":false,"status":{"created_at":"Thu May 31 17:54:18 +0000 2007","text":"is happy that tonight is the last night that you can smoke in bars and restaurants in Iceland. Washing machines won't be happy with this.","id":85708142}},{"name":"Buzz Andersen","description":null,"location":"San Francisco, CA","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/528/normal/buzz-jacket-tiny.jpg?1171962059","url":"http://buzz.vox.com","id":528,"screen_name":"buzz","protected":false,"status":{"created_at":"Sun Jun 03 18:56:37 +0000 2007","text":"At Madison Square Park, waiting to meet my Shake Shack compatriots.","id":89543882}},{"name":"Case","description":null,"location":"San Francisco, CA, USA","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/409/normal/me-skype.jpg?1177561455","url":"http://vedana.net/","id":409,"screen_name":"Case","protected":true,"status":{"created_at":"Sun Jun 03 18:25:46 +0000 2007","text":"still buzzing from the arcade fire show, listening to neon bible and dancing around in delight!","id":89518902}},{"name":"Chris DiBona","description":null,"location":null,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/44423/normal/103ID_DiBona.jpg?1171961329","url":null,"id":44423,"screen_name":"cdibona","protected":true,"status":{"created_at":"Fri Mar 16 16:42:55 +0000 2007","text":"Cripes, trapped on the tarmac at iad. Today, we sit in hell:","id":8638201}},{"name":"Dave McClure","description":"Master of 500 Hats","location":"silicon valley, sf bay area","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/1081/normal/guido3.jpg?1171953420","url":"http://500hats.typepad.com","id":1081,"screen_name":"davemc500hats","protected":false,"status":{"created_at":"Fri Jun 01 09:35:11 +0000 2007","text":"just blogged about Sacks, Facebook, Wisdom of Crowds http://tinyurl.com/28v44h","id":86555632}},{"name":"Dion Almaer","description":"ajaxian, googley, and techno","location":"Palo Alto, CA","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/4216361/normal/logo-48x48.jpg?1176313520","url":"http://almaer.com/blog","id":4216361,"screen_name":"dalmaer","protected":false,"status":{"created_at":"Wed Apr 11 17:48:16 +0000 2007","text":"Enjoying being able to talk about the Google Developer Day!","id":24861261}},{"name":"eric L","description":"mobile expert, internet idiot","location":"san francisco","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/8291/normal/bandit.jpg?1165079078","url":"http://www.n1s.net","id":8291,"screen_name":"n1s","protected":false,"status":{"created_at":"Sun Jun 03 03:08:04 +0000 2007","text":"A howat once said there's no scorin with the sporin.","id":88771502}},{"name":"Evan Williams","description":"Founder of Obvious ","location":"San Francisco, CA","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/20/normal/ev-sky.jpg?1175282926","url":"http://evhead.com","id":20,"screen_name":"ev","protected":false,"status":{"created_at":"Sun Jun 03 07:04:50 +0000 2007","text":"This bathroom has an overload of marble","id":88950312}},{"name":"Greg Stein","description":null,"location":null,"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/12449/normal/gstein-by-pdcawley-cropped.jpg?1171954097","url":null,"id":12449,"screen_name":"gstein","protected":false},{"name":"Ian McKellar","description":"","location":"San Francisco, CA","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/259/normal/trapped.jpg?1171957894","url":"http://ian.mckellar.org/","id":259,"screen_name":"ianmckellar","protected":false,"status":{"created_at":"Sat Jun 02 20:55:30 +0000 2007","text":"lolfeeds got shut down for using too much cpu so I had to get around to adding a caching layer. it fixed some character encoding issues too!","id":88496342}},{"name":"jark","description":"Co-Founder of deviantART","location":"Tokyo, Japan","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/39653/normal/jark-static.jpg?1171960858","url":"http://jarkolicious.com/","id":39653,"screen_name":"jark","protected":false,"status":{"created_at":"Sun Jun 03 14:51:25 +0000 2007","text":"heads to bed, but first starts a process to burn 300 to DVD.","id":89318752}},{"name":"Jeff Barr","description":"Amazon Web Services Evangelist, Blogger, Father of 5.","location":"Sammamish, Washington, USA","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/48443/normal/jeff_barr.jpg?1171961668","url":"http://www.jeff-barr.com","id":48443,"screen_name":"jeffbarr","protected":false,"status":{"created_at":"Sun Jun 03 16:43:47 +0000 2007","text":"Preparing for trip to DC tomorrow AM - lots of reading materials, TODO list, iPod fresh, camera charged, laptop packed. Arrange for taxi.","id":89432112}},{"name":"Jeremy Zawodny","description":"I fly and geek.","location":"San Jose, CA","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/97933/normal/Zawodny-md.jpg?1166680073","url":"http://jeremy.zawodny.com/blog/","id":97933,"screen_name":"jzawodn","protected":false,"status":{"created_at":"Sat Jun 02 20:04:53 +0000 2007","text":"errands and packing for a week in the desert next week...","id":88458352}},{"name":"John Gruber","description":"Raconteur.","location":"Philadelphia","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/33423/normal/gruber-wanamaker-monorail.jpg?1171960346","url":"http://daringfireball.net","id":33423,"screen_name":"gruber","protected":false,"status":{"created_at":"Sun Jun 03 14:51:07 +0000 2007","text":"Needless to say, it was great time.","id":89318502}},{"name":"Josh H","description":null,"location":null,"profile_image_url":"http://assets1.twitter.com/images/default_image.gif?1180755379","url":null,"id":2831771,"screen_name":"josh59x","protected":false},{"name":"Josh Lucas","description":"Just adding another bit of distraction...","location":"Pasadena, CA","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/47023/normal/cubs_me.jpg?1171961535","url":"http://www.stonecottage.com/josh/","id":47023,"screen_name":"lucasjosh","protected":false,"status":{"created_at":"Sun Jun 03 15:16:47 +0000 2007","text":"wearing my new old-skool upcoming shirt","id":89345632}},{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false,"status":{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102}},{"name":"Kevin Burton","description":null,"location":null,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/62763/normal/me-profile.jpg?1173387685","url":null,"id":62763,"screen_name":"burtonator","protected":false,"status":{"created_at":"Sun Jun 03 08:43:41 +0000 2007","text":"zoe is playing with a plastic ring from a milk bottle.... cheap toy!","id":89021982}},{"name":"mfagan","description":"","location":"Canada","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/677403/normal/me_with_hat.jpg?1171966071","url":"http://faganm.com/","id":677403,"screen_name":"mfagan","protected":false},{"name":"Mihai","description":"","location":"New York, NY","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/28203/normal/mihaip.jpg?1171958069","url":"http://persistent.info/","id":28203,"screen_name":"mihai","protected":false,"status":{"created_at":"Sat Jun 02 19:46:47 +0000 2007","text":"Back on campus for reunions. Don't feel old just yet. Phew.","id":88444532}},{"name":"Mr Messina","description":"As if concentrating wasn't hard enough already.","location":"94107","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/1186/normal/devil_150.jpg?1171953828","url":"http://factoryjoe.com/","id":1186,"screen_name":"factoryjoe","protected":false,"status":{"created_at":"Sun Jun 03 01:42:47 +0000 2007","text":"OpenID won the disruptor award at The NextWeb conference! http://tinyurl.com/yq8e89","id":88706172}},{"name":"Niall","description":"Squeezing the most out of everything but my phone","location":"San Francisco, CA","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/1085/normal/niall_ringer.jpg?1171953434","url":"http://www.niallkennedy.com/","id":1085,"screen_name":"niall","protected":true,"status":{"created_at":"Sat Jun 02 04:32:57 +0000 2007","text":"your server migration is at 11. oh, we changed our minds, we're doing it at 9:30. ummm....yay?","id":87696902}},{"name":"Nick Douglas","description":"I am clever. Are you clever too?","location":"San Francisco","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/1084/normal/Nick-tiny-face.jpg?1171953430","url":"http://lookshiny.com","id":1084,"screen_name":"nick","protected":false,"status":{"created_at":"Sun Jun 03 07:52:33 +0000 2007","text":"Even Mark Day was at Arcade Fire, and I conned myself out of going with a cute friend 'cuz I'd told her how much I hate the band.","id":88984072}},{"name":"Paul Downey","description":"Computing Industry Bi-product","location":"Berkhamsted, UK","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/13486/normal/psd-75x75.jpg?1171954493","url":"http://blog.whatfettle.com","id":13486,"screen_name":"psd","protected":false,"status":{"created_at":"Sun Jun 03 16:24:53 +0000 2007","text":"back from tea and cake at the village hall now resuming futile search for a family holiday on 'tinternet","id":89413652}},{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/677903/normal/sean-85.png?1173252480","url":"http://www.seancoon.org","id":677903,"screen_name":"spcoon","protected":false,"status":{"created_at":"Sat Jun 02 08:27:03 +0000 2007","text":"Holy moly. Time to go fishing. Yeeeaaahhh!","id":87876222}},{"name":"Simon Willison","description":null,"location":"London","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/12497/normal/298777290_a5ed9a4e70_m.jpg?1171954113","url":"http://simonwillison.net/","id":12497,"screen_name":"simonw","protected":false,"status":{"created_at":"Fri Jun 01 21:01:59 +0000 2007","text":"Nat and I are in Brighton this weekend, anyone want to meet up?","id":87291372}},{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","url":"http://redmonk.com/sogrady","id":143883,"screen_name":"sogrady","protected":false,"status":{"created_at":"Sun Jun 03 15:57:16 +0000 2007","text":"ah, it's under \"Reply\"","id":89385112}},{"name":"Tatsuhiko Miyagawa","description":"Yet another Perl hacker","location":"San Francisco","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/731253/normal/P506iC0003735833.jpg?1170146286","url":"http://bulknews.vox.com/","id":731253,"screen_name":"miyagawa","protected":false,"status":{"created_at":"Sun Jun 03 04:06:57 +0000 2007","text":"had a good rice/veggie/pork noodle and shrimp fried rice in HoH. Feeling almost full and too ricey","id":88820042}},{"name":"Tom Coates","description":"Scruffy, grumpy, social mediaesque...","location":"London","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/12514/normal/12037949715_N01.jpg?1178111032","url":"http://www.plasticbag.org/","id":12514,"screen_name":"plasticbagUK","protected":false,"status":{"created_at":"Sun Jun 03 18:21:20 +0000 2007","text":"My heart sinks as I pass Hackney Down.","id":89515562}},{"name":"veen","description":"I used to make small things. Now I make big things.","location":"San Francisco","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/414/normal/Photo_21.jpg?1171961099","url":"http://veen.com/jeff/","id":414,"screen_name":"veen","protected":false,"status":{"created_at":"Fri Jun 01 22:38:36 +0000 2007","text":"Received an absolutely beautiful wedding invitation, made even better by an Obi Wan Kenobi postage stamp.","id":87377882}},{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/10350/normal/mypictr_140x144.jpg?1179253942","url":"http://www.veronicabelmont.com","id":10350,"screen_name":"Veronica","protected":false,"status":{"created_at":"Sun Jun 03 03:11:10 +0000 2007","text":"i just saw kari byron! my hero!","id":88774212}},{"name":"Yes","description":null,"location":null,"profile_image_url":"http://assets1.twitter.com/images/default_image.gif?1180755379","url":null,"id":765884,"screen_name":"Yes","protected":false,"status":{"created_at":"Wed May 09 20:43:39 +0000 2007","text":"Whoo hoo! Yay!","id":57715832}},{"name":"Yoz","description":"A small yoz-type object, currently residing in San Francisco","location":"San Francisco, CA","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/12329/normal/yozlap-100.jpg?1171954052","url":"http://yoz.com/","id":12329,"screen_name":"yoz","protected":false,"status":{"created_at":"Fri Jun 01 18:13:23 +0000 2007","text":"@riffraff814: Thanks but no thanks, don't drink coffee, my body is a temple filled with magical prancing ponies yay peanut M&M overdose","id":87132942}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/friends_timeline-kesuke.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/friends_timeline-kesuke.json --- python-twitter-0.5/testdata/friends_timeline-kesuke.json 2007-06-03 19:54:09.000000000 +0100 +++ python-twitter-0.6/testdata/friends_timeline-kesuke.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","screen_name":"kesuke"}},{"created_at":"Sat Jun 02 17:13:19 +0000 2007","text":"At WhereCamp on yahoo's campus. Great crowd.","id":88313822,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Jun 01 19:17:36 +0000 2007","text":"Yay Feedburner.","id":87194962,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed May 09 19:47:06 +0000 2007","text":"Just ordered a Wii from Amazon. Thanks to Greg!","id":57662052,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 21:01:56 +0000 2007","text":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002(No really, it is.)","id":42342562,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 19:02:43 +0000 2007","text":"Whoops. My python-twitter library can't handle utf8. On the upside, sending random twitters in Hungarian gets people to un-follow me...","id":42237742,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 15:34:12 +0000 2007","text":"A l\u00e9gp\u00e1rn\u00e1s haj\u00f3m tele van angoln\u00e1kkal.","id":41984622,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Apr 23 03:45:20 +0000 2007","text":"And that, my friends, is baseball at its finest.","id":36527662,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Apr 02 15:32:55 +0000 2007","text":"Fixed a python-twitter bug that returned broken relative_created_at in some cases. New version at: http:\/\/code.google.com\/p\/python-twitter\/","id":17729991,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Mar 30 02:46:53 +0000 2007","text":"Just got home, and hoping that Buzz's office hours are enough to get me to go out again tonight.","id":15587961,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Mar 12 15:45:01 +0000 2007","text":"The latest !!! album is making me happy this morning.","id":7015091,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Mar 07 03:25:59 +0000 2007","text":"Drove home to Arcade Fire's Neon Bible. Flawed around the middle, it is still the best thing I've heard this year. I really needed this.","id":5895171,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Mar 05 16:15:57 +0000 2007","text":"Just updated all my Linux boxes to handle the changes to daylight savings time. Have you? Write me if you need a hand.","id":5854199,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Mar 02 04:47:39 +0000 2007","text":"Yay! My first earthquake. Holy crap! They're kinda scary, aren't they?","id":5787870,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Thu Mar 01 04:36:14 +0000 2007","text":"Headed to Alembic early for Buzz's office hours.","id":5765262,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 28 22:57:54 +0000 2007","text":"Are the Twitter APIs a tad temperamental today? ","id":5760759,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Sat Feb 24 03:52:16 +0000 2007","text":"On route to Mad Dog in the Fog to see Brendan and Corrie. 3 years in the Bay Area and I still haven't felt a quake.","id":5671516,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Sat Feb 17 16:10:12 +0000 2007","text":"Just preordered Modest Mouse _We Were Dead Before the Ship Even Sank_ and Arcade Fire _Neon Bible_. I need to hibernate until March now.","id":5556806,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 14 22:59:09 +0000 2007","text":"Uh-oh. My IM away message just got Twittered. That's not a good thing.","id":5510368,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 14 22:39:25 +0000 2007","text":"In meetings","id":5510068,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http:\/\/unto.net\/","id":673483,"protected":false,"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"}}] \ No newline at end of file +[{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","screen_name":"kesuke"}},{"created_at":"Sat Jun 02 17:13:19 +0000 2007","text":"At WhereCamp on yahoo's campus. Great crowd.","id":88313822,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Jun 01 19:17:36 +0000 2007","text":"Yay Feedburner.","id":87194962,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed May 09 19:47:06 +0000 2007","text":"Just ordered a Wii from Amazon. Thanks to Greg!","id":57662052,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 21:01:56 +0000 2007","text":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002(No really, it is.)","id":42342562,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 19:02:43 +0000 2007","text":"Whoops. My python-twitter library can't handle utf8. On the upside, sending random twitters in Hungarian gets people to un-follow me...","id":42237742,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Apr 27 15:34:12 +0000 2007","text":"A l\u00e9gp\u00e1rn\u00e1s haj\u00f3m tele van angoln\u00e1kkal.","id":41984622,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Apr 23 03:45:20 +0000 2007","text":"And that, my friends, is baseball at its finest.","id":36527662,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Apr 02 15:32:55 +0000 2007","text":"Fixed a python-twitter bug that returned broken relative_created_at in some cases. New version at: http://code.google.com/p/python-twitter/","id":17729991,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Mar 30 02:46:53 +0000 2007","text":"Just got home, and hoping that Buzz's office hours are enough to get me to go out again tonight.","id":15587961,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Mar 12 15:45:01 +0000 2007","text":"The latest !!! album is making me happy this morning.","id":7015091,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Mar 07 03:25:59 +0000 2007","text":"Drove home to Arcade Fire's Neon Bible. Flawed around the middle, it is still the best thing I've heard this year. I really needed this.","id":5895171,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Mon Mar 05 16:15:57 +0000 2007","text":"Just updated all my Linux boxes to handle the changes to daylight savings time. Have you? Write me if you need a hand.","id":5854199,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Fri Mar 02 04:47:39 +0000 2007","text":"Yay! My first earthquake. Holy crap! They're kinda scary, aren't they?","id":5787870,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Thu Mar 01 04:36:14 +0000 2007","text":"Headed to Alembic early for Buzz's office hours.","id":5765262,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 28 22:57:54 +0000 2007","text":"Are the Twitter APIs a tad temperamental today? ","id":5760759,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Sat Feb 24 03:52:16 +0000 2007","text":"On route to Mad Dog in the Fog to see Brendan and Corrie. 3 years in the Bay Area and I still haven't felt a quake.","id":5671516,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Sat Feb 17 16:10:12 +0000 2007","text":"Just preordered Modest Mouse _We Were Dead Before the Ship Even Sank_ and Arcade Fire _Neon Bible_. I need to hibernate until March now.","id":5556806,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 14 22:59:09 +0000 2007","text":"Uh-oh. My IM away message just got Twittered. That's not a good thing.","id":5510368,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}},{"created_at":"Wed Feb 14 22:39:25 +0000 2007","text":"In meetings","id":5510068,"user":{"name":"DeWitt","description":"Indeterminate things","location":"San Francisco, CA","url":"http://unto.net/","id":673483,"protected":false,"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/public_timeline_error.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/public_timeline_error.json --- python-twitter-0.5/testdata/public_timeline_error.json 1970-01-01 01:00:00.000000000 +0100 +++ python-twitter-0.6/testdata/public_timeline_error.json 2009-02-25 16:49:04.000000000 +0000 @@ -0,0 +1 @@ +{"error": "test error"} diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/public_timeline.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/public_timeline.json --- python-twitter-0.5/testdata/public_timeline.json 2007-06-03 19:01:01.000000000 +0100 +++ python-twitter-0.6/testdata/public_timeline.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"created_at":"Sun Jun 03 17:59:37 +0000 2007","text":"Warcry Blog: Anjas first Birthday: Today was Anjas first birthday, It is insane that a year .. http:\/\/tinyurl.com\/2x7omb","id":89497702,"user":{"name":"Patrik Olterman","description":"","location":"Riga","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/2038581\/normal\/dsc03106.jpg?1180337431","url":"http:\/\/warcry.olterman.se","id":2038581,"screen_name":"olterman","protected":false}},{"created_at":"Sun Jun 03 18:01:01 +0000 2007","text":"3am AEST - AWAYE! (Listen Up) - http:\/\/tinyurl.com\/38nuza","id":89497692,"user":{"name":"ABC Radio National","description":"","location":"Australia","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3496461\/normal\/abc.jpg?1176239249","url":"http:\/\/abc.net.au\/rn\/","id":3496461,"screen_name":"abcrn","protected":false}},{"created_at":"Sun Jun 03 17:59:36 +0000 2007","text":"I am going to brush my teeth cuz i just woke up","id":89497682,"user":{"name":"alanna esposito","description":"","location":"","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3467431\/normal\/legzz.jpg?1175725871","url":null,"id":3467431,"screen_name":"luhhsespino","protected":false}},{"created_at":"Sun Jun 03 18:00:58 +0000 2007","text":"Listening to an old Blur album ('13').","id":89497672,"user":{"name":"Lex The Hex","description":"I love anything geeky, love gadgets, find it very difficult to talk to people.","location":"North Devon, UK","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/429783\/normal\/IMAGE_099.jpg?1180813787","url":"http:\/\/brooknet.org","id":429783,"screen_name":"lexthehex","protected":false}},{"created_at":"Sun Jun 03 18:00:58 +0000 2007","text":"taking pics of myself 4 my facebook.","id":89497662,"user":{"name":"Lexi Jackson","description":"","location":"","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/6525642\/normal\/cute_dog.jpg?1180803976","url":null,"id":6525642,"screen_name":"LexiJackson","protected":false}},{"created_at":"Sun Jun 03 17:59:33 +0000 2007","text":"I am getting bored. Wish i could entertain myself. Solution any one?","id":89497632,"user":{"name":"Samuel Joos","description":null,"location":null,"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/6517892\/normal\/datbenik.jpg?1180766995","url":null,"id":6517892,"screen_name":"Flashingback","protected":false}},{"created_at":"Sun Jun 03 18:00:56 +0000 2007","text":"@definetheline; STFU!!!!!!","id":89497622,"user":{"name":"Alejandro [correa]","description":"I'm like quicksand. Sandy & quick.","location":"Miami","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/639343\/normal\/avatar.jpg?1180748145","url":"http:\/\/flickr.com\/photos\/alej744","id":639343,"screen_name":"alej744","protected":false}},{"created_at":"Sun Jun 03 18:00:53 +0000 2007","text":"Haciendo monstruos","id":89497582,"user":{"name":"Jimena Vega","description":"Vean mis Monstruos: www.monstersncutties.wordpress.com","location":"M\u00e8xico","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/761245\/normal\/511658650_2c1e59ce98_t.jpg?1179977645","url":"http:\/\/www.shamballa.fulguris.net","id":761245,"screen_name":"shamballa","protected":false}},{"created_at":"Sun Jun 03 17:59:29 +0000 2007","text":"heading to chinatown for roast pork n duck n bubble tea","id":89497572,"user":{"name":"Corinne","description":"","location":"","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/815054\/normal\/oops.jpg?1173239376","url":"http:\/\/25cents.wordpress.com","id":815054,"screen_name":"xcori","protected":false}},{"created_at":"Sun Jun 03 17:59:29 +0000 2007","text":"I'm actually really enjoying Facebook. The calendars and stuff are very organizational. I'm an organization freak! Perfect for me!","id":89497562,"user":{"name":"The Mighty Mommy","description":"Mommy of Two, Wife of One, Friend to Many!","location":"Arizona","profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/772523\/normal\/miimage.jpg?1179497687","url":"http:\/\/mightymommy.qdnow.com","id":772523,"screen_name":"MightyMommy","protected":false}},{"created_at":"Sun Jun 03 18:00:52 +0000 2007","text":"Off for tonight, see you tomorrow.","id":89497552,"user":{"name":"Benjamin Gauthey","description":"","location":"","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/3194221\/normal\/me.jpg?1179996565","url":"http:\/\/www.benjamingauthey.com","id":3194221,"screen_name":"benjamingauthey","protected":false}},{"created_at":"Sun Jun 03 18:00:52 +0000 2007","text":"[Blog Updated] Weewar - \u5c0f\u578b\u5728\u7ebf\u5373\u65f6\u6218\u7565\u6e38\u620f http:\/\/tinyurl.com\/33yfa2","id":89497532,"user":{"name":"Lyang","description":"\"If we lose, then what the hell, at least we died trying...\" Digg On!","location":"Shanghai.CN","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/5144201\/normal\/elusive.png?1178901205","url":"http:\/\/a7xorz.blogspot.com","id":5144201,"screen_name":"a7xorz","protected":false}},{"created_at":"Sun Jun 03 18:00:51 +0000 2007","text":"[WangTam] Multiverse \u83b7\u6536 400 \u4e07\u7f8e\u5143\u6295\u8d44: Second Life \u7684\u6210\u529f\u5e76\u975e\u5076\u7136\uff0c\u5efa\u7acb\u5728\u865a\u62df\u4e16\u754c\u57fa\u7840\u4e0a\u7684\u793e\u4f1a\u5316\u7f51\u7edc\uff0c\u5176\u5e94\u7528\u8d8a\u6765\u8d8a\u6df1\u5165\u666e\u904d\u7528\u6237\u65e5\u5e38\u7f51\u7edc\u751f\u6d3b\u3002\u4ee5\u540e\u7c7b\u4f3c\u4e8e .. http:\/\/tinyurl.com\/36j5r6","id":89497522,"user":{"name":"\u542f\u7f81","description":"\u88c5\u50bb\u6bd4\u88c5\u903c\u66f4\u6709\u76ca\u4e8e\u8eab\u5fc3\u5065\u5eb7","location":"\u714b (Mars)","profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/917171\/normal\/QeeGi_Radlin.png?1176903251","url":"http:\/\/www.wangtam.com","id":917171,"screen_name":"QeeGi","protected":false}},{"created_at":"Sun Jun 03 18:00:51 +0000 2007","text":"Adoring the sublime architecture of the universe, including you.","id":89497512,"user":{"name":"Jon","description":"The millionth monkey.","location":"A castle. In SPACE!","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/6239122\/normal\/jonbasscropped2.jpg?1179858686","url":"http:\/\/www.jonglassett.com","id":6239122,"screen_name":"jonniejerko","protected":false}},{"created_at":"Sun Jun 03 18:00:49 +0000 2007","text":"\u9154\u3063\u305f\u3002\u5b9f\u306b\u9154\u3063\u305f\u3002\u76ee\u306e\u524d\u306e\u5b9a\u7fa9\u3092\u7c21\u5358\u306b\u65ad\u5b9a\u3059\u308b\u307b\u3069\u9154\u3063\u3066\u3057\u307e\u3063\u305f\uff01","id":89497482,"user":{"name":"wazurai","description":"\u7169\u3046\u4e8b\u3092\u4ed5\u4e8b\u306b\u98df\u3079\u3066\u3044\u3051\u308b\u69d8\u306b\u8abf\u7bc0\u4e2d\u306e\u6bce\u65e5\u3002","location":"\u65e5\u672c\u306e\u6771\u4eac\u306e\u7acb\u5ddd\u306e\u6771\u3042\u305f\u308a\u3002","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/6464452\/normal\/382616310_249-1.jpg?1180682927","url":"http:\/\/wazurai.org\/","id":6464452,"screen_name":"wazurai","protected":false}},{"created_at":"Sun Jun 03 18:00:47 +0000 2007","text":"aaarrrggghhhh...looks like a bad day for air travel.","id":89497442,"user":{"name":"Andrew DeVigal","description":"","location":"New York, NY","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/626103\/normal\/andrewdevigal_lores.jpg?1171963676","url":"http:\/\/andrew.devigal.com\/","id":626103,"screen_name":"drewvigal","protected":false}},{"created_at":"Sun Jun 03 18:00:46 +0000 2007","text":"\u3010\u97d3\u56fd\u3011\u82f1\u8a9e\u304c\u308f\u304b\u3089\u306a\u3044\u3068\u30bf\u30af\u30b7\u30fc\u904b\u8ee2\u624b\u3092\u6bb4\u3063\u305f\u30a2\u30e1\u30ea\u30ab\u4eba\u3092\u5728\u5b85\u8d77\u8a34 http:\/\/tinyurl.com\/38bhj8","id":89497432,"user":{"name":"2NN","description":"","location":"","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/5507772\/normal\/2NN.gif?1177538505","url":"http:\/\/www.2nn.jp\/","id":5507772,"screen_name":"2NN","protected":false}},{"created_at":"Sun Jun 03 18:00:46 +0000 2007","text":"Why gas prices are so pumped up: Gas experts place the blame for high prices on a national gas shortage .. http:\/\/tinyurl.com\/ysc8rm","id":89497402,"user":{"name":"Netscape","description":null,"location":null,"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/822660\/normal\/Picture_3.png?1173380596","url":null,"id":822660,"screen_name":"Netscape","protected":false}},{"created_at":"Sun Jun 03 18:00:45 +0000 2007","text":"@erfani \u0634\u0646\u06cc\u062f\u0647 \u0628\u0648\u062f\u0645","id":89497392,"user":{"name":"Mahdi kazzazi","description":"","location":"","profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/5630472\/normal\/miimage.jpg?1180550654","url":"http:\/\/www.persiandeveloper.com\/","id":5630472,"screen_name":"MMahdi","protected":false}},{"created_at":"Sun Jun 03 18:00:44 +0000 2007","text":"De a Fried k\u00f6nyve is!","id":89497372,"user":{"name":"Balint Sera","description":null,"location":null,"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/5861892\/normal\/pic.jpg?1178628338","url":null,"id":5861892,"screen_name":"damnadm","protected":false}}] \ No newline at end of file +[{"created_at":"Sun Jun 03 17:59:37 +0000 2007","text":"Warcry Blog: Anjas first Birthday: Today was Anjas first birthday, It is insane that a year .. http://tinyurl.com/2x7omb","id":89497702,"user":{"name":"Patrik Olterman","description":"","location":"Riga","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/2038581/normal/dsc03106.jpg?1180337431","url":"http://warcry.olterman.se","id":2038581,"screen_name":"olterman","protected":false}},{"created_at":"Sun Jun 03 18:01:01 +0000 2007","text":"3am AEST - AWAYE! (Listen Up) - http://tinyurl.com/38nuza","id":89497692,"user":{"name":"ABC Radio National","description":"","location":"Australia","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3496461/normal/abc.jpg?1176239249","url":"http://abc.net.au/rn/","id":3496461,"screen_name":"abcrn","protected":false}},{"created_at":"Sun Jun 03 17:59:36 +0000 2007","text":"I am going to brush my teeth cuz i just woke up","id":89497682,"user":{"name":"alanna esposito","description":"","location":"","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3467431/normal/legzz.jpg?1175725871","url":null,"id":3467431,"screen_name":"luhhsespino","protected":false}},{"created_at":"Sun Jun 03 18:00:58 +0000 2007","text":"Listening to an old Blur album ('13').","id":89497672,"user":{"name":"Lex The Hex","description":"I love anything geeky, love gadgets, find it very difficult to talk to people.","location":"North Devon, UK","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/429783/normal/IMAGE_099.jpg?1180813787","url":"http://brooknet.org","id":429783,"screen_name":"lexthehex","protected":false}},{"created_at":"Sun Jun 03 18:00:58 +0000 2007","text":"taking pics of myself 4 my facebook.","id":89497662,"user":{"name":"Lexi Jackson","description":"","location":"","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/6525642/normal/cute_dog.jpg?1180803976","url":null,"id":6525642,"screen_name":"LexiJackson","protected":false}},{"created_at":"Sun Jun 03 17:59:33 +0000 2007","text":"I am getting bored. Wish i could entertain myself. Solution any one?","id":89497632,"user":{"name":"Samuel Joos","description":null,"location":null,"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/6517892/normal/datbenik.jpg?1180766995","url":null,"id":6517892,"screen_name":"Flashingback","protected":false}},{"created_at":"Sun Jun 03 18:00:56 +0000 2007","text":"@definetheline; STFU!!!!!!","id":89497622,"user":{"name":"Alejandro [correa]","description":"I'm like quicksand. Sandy & quick.","location":"Miami","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/639343/normal/avatar.jpg?1180748145","url":"http://flickr.com/photos/alej744","id":639343,"screen_name":"alej744","protected":false}},{"created_at":"Sun Jun 03 18:00:53 +0000 2007","text":"Haciendo monstruos","id":89497582,"user":{"name":"Jimena Vega","description":"Vean mis Monstruos: www.monstersncutties.wordpress.com","location":"M\u00e8xico","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/761245/normal/511658650_2c1e59ce98_t.jpg?1179977645","url":"http://www.shamballa.fulguris.net","id":761245,"screen_name":"shamballa","protected":false}},{"created_at":"Sun Jun 03 17:59:29 +0000 2007","text":"heading to chinatown for roast pork n duck n bubble tea","id":89497572,"user":{"name":"Corinne","description":"","location":"","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/815054/normal/oops.jpg?1173239376","url":"http://25cents.wordpress.com","id":815054,"screen_name":"xcori","protected":false}},{"created_at":"Sun Jun 03 17:59:29 +0000 2007","text":"I'm actually really enjoying Facebook. The calendars and stuff are very organizational. I'm an organization freak! Perfect for me!","id":89497562,"user":{"name":"The Mighty Mommy","description":"Mommy of Two, Wife of One, Friend to Many!","location":"Arizona","profile_image_url":"http://assets2.twitter.com/system/user/profile_image/772523/normal/miimage.jpg?1179497687","url":"http://mightymommy.qdnow.com","id":772523,"screen_name":"MightyMommy","protected":false}},{"created_at":"Sun Jun 03 18:00:52 +0000 2007","text":"Off for tonight, see you tomorrow.","id":89497552,"user":{"name":"Benjamin Gauthey","description":"","location":"","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/3194221/normal/me.jpg?1179996565","url":"http://www.benjamingauthey.com","id":3194221,"screen_name":"benjamingauthey","protected":false}},{"created_at":"Sun Jun 03 18:00:52 +0000 2007","text":"[Blog Updated] Weewar - \u5c0f\u578b\u5728\u7ebf\u5373\u65f6\u6218\u7565\u6e38\u620f http://tinyurl.com/33yfa2","id":89497532,"user":{"name":"Lyang","description":"\"If we lose, then what the hell, at least we died trying...\" Digg On!","location":"Shanghai.CN","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/5144201/normal/elusive.png?1178901205","url":"http://a7xorz.blogspot.com","id":5144201,"screen_name":"a7xorz","protected":false}},{"created_at":"Sun Jun 03 18:00:51 +0000 2007","text":"[WangTam] Multiverse \u83b7\u6536 400 \u4e07\u7f8e\u5143\u6295\u8d44: Second Life \u7684\u6210\u529f\u5e76\u975e\u5076\u7136\uff0c\u5efa\u7acb\u5728\u865a\u62df\u4e16\u754c\u57fa\u7840\u4e0a\u7684\u793e\u4f1a\u5316\u7f51\u7edc\uff0c\u5176\u5e94\u7528\u8d8a\u6765\u8d8a\u6df1\u5165\u666e\u904d\u7528\u6237\u65e5\u5e38\u7f51\u7edc\u751f\u6d3b\u3002\u4ee5\u540e\u7c7b\u4f3c\u4e8e .. http://tinyurl.com/36j5r6","id":89497522,"user":{"name":"\u542f\u7f81","description":"\u88c5\u50bb\u6bd4\u88c5\u903c\u66f4\u6709\u76ca\u4e8e\u8eab\u5fc3\u5065\u5eb7","location":"\u714b (Mars)","profile_image_url":"http://assets3.twitter.com/system/user/profile_image/917171/normal/QeeGi_Radlin.png?1176903251","url":"http://www.wangtam.com","id":917171,"screen_name":"QeeGi","protected":false}},{"created_at":"Sun Jun 03 18:00:51 +0000 2007","text":"Adoring the sublime architecture of the universe, including you.","id":89497512,"user":{"name":"Jon","description":"The millionth monkey.","location":"A castle. In SPACE!","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/6239122/normal/jonbasscropped2.jpg?1179858686","url":"http://www.jonglassett.com","id":6239122,"screen_name":"jonniejerko","protected":false}},{"created_at":"Sun Jun 03 18:00:49 +0000 2007","text":"\u9154\u3063\u305f\u3002\u5b9f\u306b\u9154\u3063\u305f\u3002\u76ee\u306e\u524d\u306e\u5b9a\u7fa9\u3092\u7c21\u5358\u306b\u65ad\u5b9a\u3059\u308b\u307b\u3069\u9154\u3063\u3066\u3057\u307e\u3063\u305f\uff01","id":89497482,"user":{"name":"wazurai","description":"\u7169\u3046\u4e8b\u3092\u4ed5\u4e8b\u306b\u98df\u3079\u3066\u3044\u3051\u308b\u69d8\u306b\u8abf\u7bc0\u4e2d\u306e\u6bce\u65e5\u3002","location":"\u65e5\u672c\u306e\u6771\u4eac\u306e\u7acb\u5ddd\u306e\u6771\u3042\u305f\u308a\u3002","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/6464452/normal/382616310_249-1.jpg?1180682927","url":"http://wazurai.org/","id":6464452,"screen_name":"wazurai","protected":false}},{"created_at":"Sun Jun 03 18:00:47 +0000 2007","text":"aaarrrggghhhh...looks like a bad day for air travel.","id":89497442,"user":{"name":"Andrew DeVigal","description":"","location":"New York, NY","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/626103/normal/andrewdevigal_lores.jpg?1171963676","url":"http://andrew.devigal.com/","id":626103,"screen_name":"drewvigal","protected":false}},{"created_at":"Sun Jun 03 18:00:46 +0000 2007","text":"\u3010\u97d3\u56fd\u3011\u82f1\u8a9e\u304c\u308f\u304b\u3089\u306a\u3044\u3068\u30bf\u30af\u30b7\u30fc\u904b\u8ee2\u624b\u3092\u6bb4\u3063\u305f\u30a2\u30e1\u30ea\u30ab\u4eba\u3092\u5728\u5b85\u8d77\u8a34 http://tinyurl.com/38bhj8","id":89497432,"user":{"name":"2NN","description":"","location":"","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/5507772/normal/2NN.gif?1177538505","url":"http://www.2nn.jp/","id":5507772,"screen_name":"2NN","protected":false}},{"created_at":"Sun Jun 03 18:00:46 +0000 2007","text":"Why gas prices are so pumped up: Gas experts place the blame for high prices on a national gas shortage .. http://tinyurl.com/ysc8rm","id":89497402,"user":{"name":"Netscape","description":null,"location":null,"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/822660/normal/Picture_3.png?1173380596","url":null,"id":822660,"screen_name":"Netscape","protected":false}},{"created_at":"Sun Jun 03 18:00:45 +0000 2007","text":"@erfani \u0634\u0646\u06cc\u062f\u0647 \u0628\u0648\u062f\u0645","id":89497392,"user":{"name":"Mahdi kazzazi","description":"","location":"","profile_image_url":"http://assets0.twitter.com/system/user/profile_image/5630472/normal/miimage.jpg?1180550654","url":"http://www.persiandeveloper.com/","id":5630472,"screen_name":"MMahdi","protected":false}},{"created_at":"Sun Jun 03 18:00:44 +0000 2007","text":"De a Fried k\u00f6nyve is!","id":89497372,"user":{"name":"Balint Sera","description":null,"location":null,"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/5861892/normal/pic.jpg?1178628338","url":null,"id":5861892,"screen_name":"damnadm","protected":false}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/replies.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/replies.json --- python-twitter-0.5/testdata/replies.json 2007-06-03 20:38:01.000000000 +0100 +++ python-twitter-0.6/testdata/replies.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"created_at":"Mon Apr 23 06:56:04 +0000 2007","text":"@dewitt - touche.","id":36657062,"user":{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","url":"http:\/\/www.seancoon.org","id":677903,"protected":false,"profile_image_url":"http:\/\/assets3.twitter.com\/system\/user\/profile_image\/677903\/normal\/sean-85.png?1173252480","screen_name":"spcoon"}},{"created_at":"Sat Feb 10 22:26:08 +0000 2007","text":"@ DeWitt: say hey to Fairway for me","id":5418088,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Fri Feb 09 21:24:23 +0000 2007","text":"@dewitt - imho, \"meme-ish\" would be an excellent definition of the Internet in general. ","id":5398801,"user":{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http:\/\/www.veronicabelmont.com","id":10350,"protected":false,"profile_image_url":"http:\/\/assets2.twitter.com\/system\/user\/profile_image\/10350\/normal\/mypictr_140x144.jpg?1179253942","screen_name":"Veronica"}},{"created_at":"Mon Feb 05 20:13:20 +0000 2007","text":"@ DeWitt: hahaha - figured as much. only time in NYC in two months and it has to be Valentine's day.","id":5327278,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Mon Feb 05 20:03:53 +0000 2007","text":"@ DeWitt (and world): i'm in the 14th, and probably back out midday the 15th. if valentine's day isn't already spoken for, i'm game.","id":5327163,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Mon Feb 05 19:59:46 +0000 2007","text":"@ DeWitt: what days will you be in NYC?","id":5327090,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Wed Jan 31 22:29:38 +0000 2007","text":"@DeWitt: you'll like it. picked it up this afternoon in non-DRM crap form ;) better than Wincing the Night Away so far, for me","id":4895723,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http:\/\/redmonk.com\/sogrady","id":143883,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/143883\/normal\/headshot.jpg?1174273279","screen_name":"sogrady"}}] \ No newline at end of file +[{"created_at":"Mon Apr 23 06:56:04 +0000 2007","text":"@dewitt - touche.","id":36657062,"user":{"name":"sean coon","description":"trying to make a living and a difference...","location":"Greensboro, NC","url":"http://www.seancoon.org","id":677903,"protected":false,"profile_image_url":"http://assets3.twitter.com/system/user/profile_image/677903/normal/sean-85.png?1173252480","screen_name":"spcoon"}},{"created_at":"Sat Feb 10 22:26:08 +0000 2007","text":"@ DeWitt: say hey to Fairway for me","id":5418088,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Fri Feb 09 21:24:23 +0000 2007","text":"@dewitt - imho, \"meme-ish\" would be an excellent definition of the Internet in general. ","id":5398801,"user":{"name":"Veronica","description":"CNET TV host and podcasting diva of Buzz Out Loud","location":"San Francisco","url":"http://www.veronicabelmont.com","id":10350,"protected":false,"profile_image_url":"http://assets2.twitter.com/system/user/profile_image/10350/normal/mypictr_140x144.jpg?1179253942","screen_name":"Veronica"}},{"created_at":"Mon Feb 05 20:13:20 +0000 2007","text":"@ DeWitt: hahaha - figured as much. only time in NYC in two months and it has to be Valentine's day.","id":5327278,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Mon Feb 05 20:03:53 +0000 2007","text":"@ DeWitt (and world): i'm in the 14th, and probably back out midday the 15th. if valentine's day isn't already spoken for, i'm game.","id":5327163,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Mon Feb 05 19:59:46 +0000 2007","text":"@ DeWitt: what days will you be in NYC?","id":5327090,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"}},{"created_at":"Wed Jan 31 22:29:38 +0000 2007","text":"@DeWitt: you'll like it. picked it up this afternoon in non-DRM crap form ;) better than Wincing the Night Away so far, for me","id":4895723,"user":{"name":"steve o'grady","description":"analyst and co-founder of RedMonk","location":"Denver, CO","url":"http://redmonk.com/sogrady","id":143883,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/143883/normal/headshot.jpg?1174273279","screen_name":"sogrady"}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/show-89512102.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/show-89512102.json --- python-twitter-0.5/testdata/show-89512102.json 2007-06-03 19:48:06.000000000 +0100 +++ python-twitter-0.6/testdata/show-89512102.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} \ No newline at end of file +{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/show-dewitt.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/show-dewitt.json --- python-twitter-0.5/testdata/show-dewitt.json 2007-06-03 21:24:20.000000000 +0100 +++ python-twitter-0.6/testdata/show-dewitt.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"friends_count":40,"profile_background_color":"FFFFFF","name":"DeWitt","statuses_count":71,"followers_count":64,"profile_text_color":"121212","favourites_count":2,"profile_link_color":"666666","description":"Indeterminate things","location":"San Francisco, CA","profile_sidebar_fill_color":"CCCCCC","url":"http:\/\/unto.net\/","id":673483,"profile_sidebar_border_color":"333333","protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http:\/\/assets0.twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg?1171965914","screen_name":"dewitt"} \ No newline at end of file +{"friends_count":40,"profile_background_color":"FFFFFF","name":"DeWitt","statuses_count":71,"followers_count":64,"profile_text_color":"121212","favourites_count":2,"profile_link_color":"666666","description":"Indeterminate things","location":"San Francisco, CA","profile_sidebar_fill_color":"CCCCCC","url":"http://unto.net/","id":673483,"profile_sidebar_border_color":"333333","protected":false,"status":{"created_at":"Sun Jun 03 19:50:23 +0000 2007","text":"If a theme song played when I walked around all day, I'd want it to be All My Friends by LCD Soundsystem.","id":89586072},"profile_image_url":"http://assets0.twitter.com/system/user/profile_image/673483/normal/me.jpg?1171965914","screen_name":"dewitt"} \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/status-destroy.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/status-destroy.json --- python-twitter-0.5/testdata/status-destroy.json 2007-07-15 07:10:13.000000000 +0100 +++ python-twitter-0.6/testdata/status-destroy.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"created_at":"Wed Jun 13 17:08:02 +0000 2007","text":"Just a final test before 0.4 release!","id":103208352,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} +{"created_at":"Wed Jun 13 17:08:02 +0000 2007","text":"Just a final test before 0.4 release!","id":103208352,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/update.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/update.json --- python-twitter-0.5/testdata/update.json 2007-06-03 20:30:03.000000000 +0100 +++ python-twitter-0.6/testdata/update.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} \ No newline at end of file +{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","url":null,"id":718443,"screen_name":"kesuke","protected":false}} \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/testdata/user_timeline-kesuke.json /tmp/7opw10sJTP/python-twitter-0.6/testdata/user_timeline-kesuke.json --- python-twitter-0.5/testdata/user_timeline-kesuke.json 2007-06-03 19:29:47.000000000 +0100 +++ python-twitter-0.6/testdata/user_timeline-kesuke.json 2009-02-25 16:49:04.000000000 +0000 @@ -1 +1 @@ -[{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"profile_image_url":"http:\/\/assets1.twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png?1169966399","screen_name":"kesuke"}}] \ No newline at end of file +[{"created_at":"Sun Jun 03 18:15:29 +0000 2007","text":"\u041c\u043e\u0451 \u0441\u0443\u0434\u043d\u043e \u043d\u0430 \u0432\u043e\u0437\u0434\u0443\u0448\u043d\u043e\u0439 \u043f\u043e\u0434\u0443\u0448\u043a\u0435 \u043f\u043e\u043b\u043d\u043e \u0443\u0433\u0440\u0435\u0439","id":89512102,"user":{"name":"Kesuke Miyagi","description":"\u79c1\u306e\u30db\u30d0\u30fc\u30af\u30e9\u30d5\u30c8 \u306f\u9c3b\u304c\u4e00\u676f\u3067\u3059\u3002","location":"Okinawa, Japan","url":null,"id":718443,"protected":false,"profile_image_url":"http://assets1.twitter.com/system/user/profile_image/718443/normal/kesuke.png?1169966399","screen_name":"kesuke"}}] \ No newline at end of file diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/twitter.py /tmp/7opw10sJTP/python-twitter-0.6/twitter.py --- python-twitter-0.5/twitter.py 2007-07-15 18:38:54.000000000 +0100 +++ python-twitter-0.6/twitter.py 2009-06-12 20:30:30.000000000 +0100 @@ -1,27 +1,54 @@ -#!/usr/bin/python +#!/usr/bin/python2.4 # # Copyright 2007 Google Inc. All Rights Reserved. +# +# 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. '''A library that provides a python interface to the Twitter API''' __author__ = 'dewitt@google.com' -__version__ = '0.5' +__version__ = '0.6-devel' import base64 -import md5 +import calendar import os +import rfc822 import simplejson import sys import tempfile +import textwrap import time import urllib import urllib2 import urlparse -import twitter + +try: + from hashlib import md5 +except ImportError: + from md5 import md5 + + +CHARACTER_LIMIT = 140 + class TwitterError(Exception): '''Base class for Twitter errors''' + + @property + def message(self): + '''Returns the first argument used to construct this error.''' + return self.args[0] class Status(object): @@ -31,6 +58,12 @@ status.created_at status.created_at_in_seconds # read only + status.favorited + status.in_reply_to_screen_name + status.in_reply_to_user_id + status.in_reply_to_status_id + status.truncated + status.source status.id status.text status.relative_created_at # read only @@ -38,9 +71,15 @@ ''' def __init__(self, created_at=None, + favorited=None, id=None, text=None, user=None, + in_reply_to_screen_name=None, + in_reply_to_user_id=None, + in_reply_to_status_id=None, + truncated=None, + source=None, now=None): '''An object to hold a Twitter status message. @@ -51,6 +90,7 @@ Args: created_at: The time this status message was posted + favorited: Whether this is a favorite of the authenticated user id: The unique id of this status message text: The text of this status message relative_created_at: @@ -62,10 +102,16 @@ wall clock time. ''' self.created_at = created_at + self.favorited = favorited self.id = id self.text = text self.user = user self.now = now + self.in_reply_to_screen_name = in_reply_to_screen_name + self.in_reply_to_user_id = in_reply_to_user_id + self.in_reply_to_status_id = in_reply_to_status_id + self.truncated = truncated + self.source = source def GetCreatedAt(self): '''Get the time this status message was posted. @@ -92,12 +138,31 @@ Returns: The time this status message was posted, in seconds since the epoch. ''' - return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y')) + return calendar.timegm(rfc822.parsedate(self.created_at)) created_at_in_seconds = property(GetCreatedAtInSeconds, doc="The time this status message was " "posted, in seconds since the epoch") + def GetFavorited(self): + '''Get the favorited setting of this status message. + + Returns: + True if this status message is favorited; False otherwise + ''' + return self._favorited + + def SetFavorited(self, favorited): + '''Set the favorited state of this status message. + + Args: + favorited: boolean True/False favorited state of this status message + ''' + self._favorited = favorited + + favorited = property(GetFavorited, SetFavorited, + doc='The favorited state of this status message.') + def GetId(self): '''Get the unique id of this status message. @@ -117,6 +182,51 @@ id = property(GetId, SetId, doc='The unique id of this status message.') + def GetInReplyToScreenName(self): + return self._in_reply_to_screen_name + + def SetInReplyToScreenName(self, in_reply_to_screen_name): + self._in_reply_to_screen_name = in_reply_to_screen_name + + in_reply_to_screen_name = property(GetInReplyToScreenName, SetInReplyToScreenName, + doc='') + + def GetInReplyToUserId(self): + return self._in_reply_to_user_id + + def SetInReplyToUserId(self, in_reply_to_user_id): + self._in_reply_to_user_id = in_reply_to_user_id + + in_reply_to_user_id = property(GetInReplyToUserId, SetInReplyToUserId, + doc='') + + def GetInReplyToStatusId(self): + return self._in_reply_to_status_id + + def SetInReplyToStatusId(self, in_reply_to_status_id): + self._in_reply_to_status_id = in_reply_to_status_id + + in_reply_to_status_id = property(GetInReplyToStatusId, SetInReplyToStatusId, + doc='') + + def GetTruncated(self): + return self._truncated + + def SetTruncated(self, truncated): + self._truncated = truncated + + truncated = property(GetTruncated, SetTruncated, + doc='') + + def GetSource(self): + return self._source + + def SetSource(self, source): + self._source = source + + source = property(GetSource, SetSource, + doc='') + def GetText(self): '''Get the text of this status message. @@ -143,7 +253,7 @@ A human readable string representing the posting time ''' fudge = 1.25 - delta = int(self.now) - int(self.created_at_in_seconds) + delta = long(self.now) - long(self.created_at_in_seconds) if delta < (1 * fudge): return 'about a second ago' @@ -197,7 +307,7 @@ in seconds since the epoch. ''' if self._now is None: - self._now = time.mktime(time.gmtime()) + self._now = time.time() return self._now def SetNow(self, now): @@ -224,7 +334,13 @@ self.created_at == other.created_at and \ self.id == other.id and \ self.text == other.text and \ - self.user == other.user + self.user == other.user and \ + self.in_reply_to_screen_name == other.in_reply_to_screen_name and \ + self.in_reply_to_user_id == other.in_reply_to_user_id and \ + self.in_reply_to_status_id == other.in_reply_to_status_id and \ + self.truncated == other.truncated and \ + self.favorited == other.favorited and \ + self.source == other.source except AttributeError: return False @@ -257,12 +373,26 @@ data = {} if self.created_at: data['created_at'] = self.created_at + if self.favorited: + data['favorited'] = self.favorited if self.id: data['id'] = self.id if self.text: data['text'] = self.text if self.user: data['user'] = self.user.AsDict() + if self.in_reply_to_screen_name: + data['in_reply_to_screen_name'] = self.in_reply_to_screen_name + if self.in_reply_to_user_id: + data['in_reply_to_user_id'] = self.in_reply_to_user_id + if self.in_reply_to_status_id: + data['in_reply_to_status_id'] = self.in_reply_to_status_id + if self.truncated is not None: + data['truncated'] = self.truncated + if self.favorited is not None: + data['favorited'] = self.favorited + if self.source: + data['source'] = self.source return data @staticmethod @@ -279,8 +409,14 @@ else: user = None return Status(created_at=data.get('created_at', None), + favorited=data.get('favorited', None), id=data.get('id', None), text=data.get('text', None), + in_reply_to_screen_name=data.get('in_reply_to_screen_name', None), + in_reply_to_user_id=data.get('in_reply_to_user_id', None), + in_reply_to_status_id=data.get('in_reply_to_status_id', None), + truncated=data.get('truncated', None), + source=data.get('source', None), user=user) @@ -295,8 +431,21 @@ user.location user.description user.profile_image_url + user.profile_background_tile + user.profile_background_image_url + user.profile_sidebar_fill_color + user.profile_background_color + user.profile_link_color + user.profile_text_color + user.protected + user.utc_offset + user.time_zone user.url user.status + user.statuses_count + user.followers_count + user.friends_count + user.favourites_count ''' def __init__(self, id=None, @@ -305,6 +454,19 @@ location=None, description=None, profile_image_url=None, + profile_background_tile=None, + profile_background_image_url=None, + profile_sidebar_fill_color=None, + profile_background_color=None, + profile_link_color=None, + profile_text_color=None, + protected=None, + utc_offset=None, + time_zone=None, + followers_count=None, + friends_count=None, + statuses_count=None, + favourites_count=None, url=None, status=None): self.id = id @@ -313,6 +475,19 @@ self.location = location self.description = description self.profile_image_url = profile_image_url + self.profile_background_tile = profile_background_tile + self.profile_background_image_url = profile_background_image_url + self.profile_sidebar_fill_color = profile_sidebar_fill_color + self.profile_background_color = profile_background_color + self.profile_link_color = profile_link_color + self.profile_text_color = profile_text_color + self.protected = protected + self.utc_offset = utc_offset + self.time_zone = time_zone + self.followers_count = followers_count + self.friends_count = friends_count + self.statuses_count = statuses_count + self.favourites_count = favourites_count self.url = url self.status = status @@ -450,6 +625,100 @@ profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl, doc='The url of the thumbnail of this user.') + def GetProfileBackgroundTile(self): + '''Boolean for whether to tile the profile background image. + + Returns: + True if the background is to be tiled, False if not, None if unset. + ''' + return self._profile_background_tile + + def SetProfileBackgroundTile(self, profile_background_tile): + '''Set the boolean flag for whether to tile the profile background image. + + Args: + profile_background_tile: Boolean flag for whether to tile or not. + ''' + self._profile_background_tile = profile_background_tile + + profile_background_tile = property(GetProfileBackgroundTile, SetProfileBackgroundTile, + doc='Boolean for whether to tile the background image.') + + def GetProfileBackgroundImageUrl(self): + return self._profile_background_image_url + + def SetProfileBackgroundImageUrl(self, profile_background_image_url): + self._profile_background_image_url = profile_background_image_url + + profile_background_image_url = property(GetProfileBackgroundImageUrl, SetProfileBackgroundImageUrl, + doc='The url of the profile background of this user.') + + def GetProfileSidebarFillColor(self): + return self._profile_sidebar_fill_color + + def SetProfileSidebarFillColor(self, profile_sidebar_fill_color): + self._profile_sidebar_fill_color = profile_sidebar_fill_color + + profile_sidebar_fill_color = property(GetProfileSidebarFillColor, SetProfileSidebarFillColor) + + def GetProfileBackgroundColor(self): + return self._profile_background_color + + def SetProfileBackgroundColor(self, profile_background_color): + self._profile_background_color = profile_background_color + + profile_background_color = property(GetProfileBackgroundColor, SetProfileBackgroundColor) + + def GetProfileLinkColor(self): + return self._profile_link_color + + def SetProfileLinkColor(self, profile_link_color): + self._profile_link_color = profile_link_color + + profile_link_color = property(GetProfileLinkColor, SetProfileLinkColor) + + def GetProfileTextColor(self): + return self._profile_text_color + + def SetProfileTextColor(self, profile_text_color): + self._profile_text_color = profile_text_color + + profile_text_color = property(GetProfileTextColor, SetProfileTextColor) + + def GetProtected(self): + return self._protected + + def SetProtected(self, protected): + self._protected = protected + + protected = property(GetProtected, SetProtected) + + def GetUtcOffset(self): + return self._utc_offset + + def SetUtcOffset(self, utc_offset): + self._utc_offset = utc_offset + + utc_offset = property(GetUtcOffset, SetUtcOffset) + + def GetTimeZone(self): + '''Returns the current time zone string for the user. + + Returns: + The descriptive time zone string for the user. + ''' + return self._time_zone + + def SetTimeZone(self, time_zone): + '''Sets the user's time zone string. + + Args: + time_zone: The descriptive time zone to assign for the user. + ''' + self._time_zone = time_zone + + time_zone = property(GetTimeZone, SetTimeZone) + def GetStatus(self): '''Get the latest twitter.Status of this user. @@ -469,6 +738,82 @@ status = property(GetStatus, SetStatus, doc='The latest twitter.Status of this user.') + def GetFriendsCount(self): + '''Get the friend count for this user. + + Returns: + The number of users this user has befriended. + ''' + return self._friends_count + + def SetFriendsCount(self, count): + '''Set the friend count for this user. + + Args: + count: The number of users this user has befriended. + ''' + self._friends_count = count + + friends_count = property(GetFriendsCount, SetFriendsCount, + doc='The number of friends for this user.') + + def GetFollowersCount(self): + '''Get the follower count for this user. + + Returns: + The number of users following this user. + ''' + return self._followers_count + + def SetFollowersCount(self, count): + '''Set the follower count for this user. + + Args: + count: The number of users following this user. + ''' + self._followers_count = count + + followers_count = property(GetFollowersCount, SetFollowersCount, + doc='The number of users following this user.') + + def GetStatusesCount(self): + '''Get the number of status updates for this user. + + Returns: + The number of status updates for this user. + ''' + return self._statuses_count + + def SetStatusesCount(self, count): + '''Set the status update count for this user. + + Args: + count: The number of updates for this user. + ''' + self._statuses_count = count + + statuses_count = property(GetStatusesCount, SetStatusesCount, + doc='The number of updates for this user.') + + def GetFavouritesCount(self): + '''Get the number of favourites for this user. + + Returns: + The number of favourites for this user. + ''' + return self._favourites_count + + def SetFavouritesCount(self, count): + '''Set the favourite count for this user. + + Args: + count: The number of favourites for this user. + ''' + self._favourites_count = count + + favourites_count = property(GetFavouritesCount, SetFavouritesCount, + doc='The number of favourites for this user.') + def __ne__(self, other): return not self.__eq__(other) @@ -481,7 +826,20 @@ self.location == other.location and \ self.description == other.description and \ self.profile_image_url == other.profile_image_url and \ + self.profile_background_tile == other.profile_background_tile and \ + self.profile_background_image_url == other.profile_background_image_url and \ + self.profile_sidebar_fill_color == other.profile_sidebar_fill_color and \ + self.profile_background_color == other.profile_background_color and \ + self.profile_link_color == other.profile_link_color and \ + self.profile_text_color == other.profile_text_color and \ + self.protected == other.protected and \ + self.utc_offset == other.utc_offset and \ + self.time_zone == other.time_zone and \ self.url == other.url and \ + self.statuses_count == other.statuses_count and \ + self.followers_count == other.followers_count and \ + self.favourites_count == other.favourites_count and \ + self.friends_count == other.friends_count and \ self.status == other.status except AttributeError: return False @@ -525,10 +883,34 @@ data['description'] = self.description if self.profile_image_url: data['profile_image_url'] = self.profile_image_url + if self.profile_background_tile is not None: + data['profile_background_tile'] = self.profile_background_tile + if self.profile_background_image_url: + data['profile_sidebar_fill_color'] = self.profile_background_image_url + if self.profile_background_color: + data['profile_background_color'] = self.profile_background_color + if self.profile_link_color: + data['profile_link_color'] = self.profile_link_color + if self.profile_text_color: + data['profile_text_color'] = self.profile_text_color + if self.protected is not None: + data['protected'] = self.protected + if self.utc_offset: + data['utc_offset'] = self.utc_offset + if self.time_zone: + data['time_zone'] = self.time_zone if self.url: data['url'] = self.url if self.status: data['status'] = self.status.AsDict() + if self.friends_count: + data['friends_count'] = self.friends_count + if self.followers_count: + data['followers_count'] = self.followers_count + if self.statuses_count: + data['statuses_count'] = self.statuses_count + if self.favourites_count: + data['favourites_count'] = self.favourites_count return data @staticmethod @@ -549,7 +931,20 @@ screen_name=data.get('screen_name', None), location=data.get('location', None), description=data.get('description', None), + statuses_count=data.get('statuses_count', None), + followers_count=data.get('followers_count', None), + favourites_count=data.get('favourites_count', None), + friends_count=data.get('friends_count', None), profile_image_url=data.get('profile_image_url', None), + profile_background_tile = data.get('profile_background_tile', None), + profile_background_image_url = data.get('profile_background_image_url', None), + profile_sidebar_fill_color = data.get('profile_sidebar_fill_color', None), + profile_background_color = data.get('profile_background_color', None), + profile_link_color = data.get('profile_link_color', None), + profile_text_color = data.get('profile_text_color', None), + protected = data.get('protected', None), + utc_offset = data.get('utc_offset', None), + time_zone = data.get('time_zone', None), url=data.get('url', None), status=status) @@ -644,7 +1039,7 @@ Returns: The time this direct message was posted, in seconds since the epoch. ''' - return time.mktime(time.strptime(self.created_at, '%a %b %d %H:%M:%S +0000 %Y')) + return calendar.timegm(rfc822.parsedate(self.created_at)) created_at_in_seconds = property(GetCreatedAtInSeconds, doc="The time this direct message was " @@ -863,6 +1258,7 @@ There are many other methods, including: + >>> api.PostUpdates(status) >>> api.PostDirectMessage(user, text) >>> api.GetUser(user) >>> api.GetReplies() @@ -878,6 +1274,7 @@ >>> api.DestroyDirectMessage(id) >>> api.DestroyFriendship(user) >>> api.CreateFriendship(user) + >>> api.GetUserByEmail(email) ''' DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute @@ -902,6 +1299,7 @@ self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT self._InitializeRequestHeaders(request_headers) self._InitializeUserAgent() + self._InitializeDefaultParameters() self._input_encoding = input_encoding self.SetCredentials(username, password) @@ -922,9 +1320,14 @@ url = 'http://twitter.com/statuses/public_timeline.json' json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [Status.NewFromJsonDict(x) for x in data] - def GetFriendsTimeline(self, user=None, since=None): + def GetFriendsTimeline(self, + user=None, + count=None, + since=None, + since_id=None): '''Fetch the sequence of twitter.Status messages for a user's friends The twitter.Api instance must be authenticated if the user is private. @@ -933,10 +1336,16 @@ user: Specifies the ID or screen name of the user for whom to return the friends_timeline. If unspecified, the username and password - must be set in the twitter.Api instance. [optional] + must be set in the twitter.Api instance. [Optional] + count: + Specifies the number of statuses to retrieve. May not be + greater than 200. [Optional] since: Narrows the returned results to just those statuses created - after the specified HTTP-formatted date. [optional] + after the specified HTTP-formatted date. [Optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] Returns: A sequence of twitter.Status instances, one for each message @@ -948,13 +1357,23 @@ else: url = 'http://twitter.com/statuses/friends_timeline.json' parameters = {} + if count is not None: + try: + if int(count) > 200: + raise TwitterError("'count' may not be greater than 200") + except ValueError: + raise TwitterError("'count' must be an integer") + parameters['count'] = count if since: parameters['since'] = since + if since_id: + parameters['since_id'] = since_id json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [Status.NewFromJsonDict(x) for x in data] - def GetUserTimeline(self, user=None, count=None, since=None): + def GetUserTimeline(self, user=None, count=None, since=None, since_id=None): '''Fetch the sequence of public twitter.Status messages for a single user. The twitter.Api instance must be authenticated if the user is private. @@ -967,6 +1386,9 @@ since: Narrows the returned results to just those statuses created after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] Returns: A sequence of twitter.Status instances, one for each message up to count @@ -981,6 +1403,8 @@ parameters['count'] = count if since: parameters['since'] = since + if since_id: + parameters['since_id'] = since_id if user: url = 'http://twitter.com/statuses/user_timeline/%s.json' % user elif not user and not self._username: @@ -989,6 +1413,7 @@ url = 'http://twitter.com/statuses/user_timeline.json' json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [Status.NewFromJsonDict(x) for x in data] def GetStatus(self, id): @@ -1004,12 +1429,13 @@ ''' try: if id: - int(id) + long(id) except: - raise TwitterError("id must be an integer") + raise TwitterError("id must be an long integer") url = 'http://twitter.com/statuses/show/%s.json' % id json = self._FetchUrl(url) data = simplejson.loads(json) + self._CheckForTwitterError(data) return Status.NewFromJsonDict(data) def DestroyStatus(self, id): @@ -1026,51 +1452,114 @@ ''' try: if id: - int(id) + long(id) except: raise TwitterError("id must be an integer") url = 'http://twitter.com/statuses/destroy/%s.json' % id json = self._FetchUrl(url, post_data={}) data = simplejson.loads(json) + self._CheckForTwitterError(data) return Status.NewFromJsonDict(data) - def PostUpdate(self, text): + def PostUpdate(self, status, in_reply_to_status_id=None): '''Post a twitter status message from the authenticated user. The twitter.Api instance must be authenticated. Args: - text: The message text to be posted. Must be less than 140 characters. - + status: + The message text to be posted. Must be less than or equal to + 140 characters. + in_reply_to_status_id: + The ID of an existing status that the status to be posted is + in reply to. This implicitly sets the in_reply_to_user_id + attribute of the resulting status to the user ID of the + message being replied to. Invalid/missing status IDs will be + ignored. [Optional] Returns: - A twitter.Status instance representing the message posted + A twitter.Status instance representing the message posted. ''' if not self._username: raise TwitterError("The twitter.Api instance must be authenticated.") - if len(text) > 140: - raise TwitterError("Text must be less than or equal to 140 characters.") + url = 'http://twitter.com/statuses/update.json' - data = {'status': text} + + if len(status) > CHARACTER_LIMIT: + raise TwitterError("Text must be less than or equal to %d characters. " + "Consider using PostUpdates." % CHARACTER_LIMIT) + + data = {'status': status} + if in_reply_to_status_id: + data['in_reply_to_status_id'] = in_reply_to_status_id json = self._FetchUrl(url, post_data=data) data = simplejson.loads(json) + self._CheckForTwitterError(data) return Status.NewFromJsonDict(data) - def GetReplies(self): + def PostUpdates(self, status, continuation=None, **kwargs): + '''Post one or more twitter status messages from the authenticated user. + + Unlike api.PostUpdate, this method will post multiple status updates + if the message is longer than 140 characters. + + The twitter.Api instance must be authenticated. + + Args: + status: + The message text to be posted. May be longer than 140 characters. + continuation: + The character string, if any, to be appended to all but the + last message. Note that Twitter strips trailing '...' strings + from messages. Consider using the unicode \u2026 character + (horizontal ellipsis) instead. [Defaults to None] + **kwargs: + See api.PostUpdate for a list of accepted parameters. + Returns: + A of list twitter.Status instance representing the messages posted. + ''' + results = list() + if continuation is None: + continuation = '' + line_length = CHARACTER_LIMIT - len(continuation) + lines = textwrap.wrap(status, line_length) + for line in lines[0:-1]: + results.append(self.PostUpdate(line + continuation, **kwargs)) + results.append(self.PostUpdate(lines[-1], **kwargs)) + return results + + def GetReplies(self, since=None, since_id=None, page=None): '''Get a sequence of status messages representing the 20 most recent replies (status updates prefixed with @username) to the authenticating user. + Args: + page: + since: + Narrows the returned results to just those statuses created + after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] + Returns: A sequence of twitter.Status instances, one for each reply to the user. ''' url = 'http://twitter.com/statuses/replies.json' if not self._username: raise TwitterError("The twitter.Api instance must be authenticated.") - json = self._FetchUrl(url) + parameters = {} + if since: + parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [Status.NewFromJsonDict(x) for x in data] - def GetFriends(self, user=None): + def GetFriends(self, user=None, page=None): '''Fetch the sequence of twitter.User instances, one for each friend. Args: @@ -1085,14 +1574,18 @@ if not self._username: raise TwitterError("twitter.Api instance must be authenticated") if user: - url = 'http://twitter.com/statuses/friends/%s.json' % user + url = 'http://twitter.com/statuses/friends/%s.json' % user else: url = 'http://twitter.com/statuses/friends.json' - json = self._FetchUrl(url) + parameters = {} + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [User.NewFromJsonDict(x) for x in data] - def GetFollowers(self): + def GetFollowers(self, page=None): '''Fetch the sequence of twitter.User instances, one for each follower The twitter.Api instance must be authenticated. @@ -1103,8 +1596,12 @@ if not self._username: raise TwitterError("twitter.Api instance must be authenticated") url = 'http://twitter.com/statuses/followers.json' - json = self._FetchUrl(url) + parameters = {} + if page: + parameters['page'] = page + json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [User.NewFromJsonDict(x) for x in data] def GetFeatured(self): @@ -1118,6 +1615,7 @@ url = 'http://twitter.com/statuses/featured.json' json = self._FetchUrl(url) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [User.NewFromJsonDict(x) for x in data] def GetUser(self, user): @@ -1134,9 +1632,10 @@ url = 'http://twitter.com/users/show/%s.json' % user json = self._FetchUrl(url) data = simplejson.loads(json) + self._CheckForTwitterError(data) return User.NewFromJsonDict(data) - def GetDirectMessages(self, since=None): + def GetDirectMessages(self, since=None, since_id=None, page=None): '''Returns a list of the direct messages sent to the authenticating user. The twitter.Api instance must be authenticated. @@ -1145,6 +1644,9 @@ since: Narrows the returned results to just those statuses created after the specified HTTP-formatted date. [optional] + since_id: + Returns only public statuses with an ID greater than (that is, + more recent than) the specified ID. [Optional] Returns: A sequence of twitter.DirectMessage instances @@ -1155,8 +1657,13 @@ parameters = {} if since: parameters['since'] = since + if since_id: + parameters['since_id'] = since_id + if page: + parameters['page'] = page json = self._FetchUrl(url, parameters=parameters) data = simplejson.loads(json) + self._CheckForTwitterError(data) return [DirectMessage.NewFromJsonDict(x) for x in data] def PostDirectMessage(self, user, text): @@ -1177,6 +1684,7 @@ data = {'text': text, 'user': user} json = self._FetchUrl(url, post_data=data) data = simplejson.loads(json) + self._CheckForTwitterError(data) return DirectMessage.NewFromJsonDict(data) def DestroyDirectMessage(self, id): @@ -1195,6 +1703,7 @@ url = 'http://twitter.com/direct_messages/destroy/%s.json' % id json = self._FetchUrl(url, post_data={}) data = simplejson.loads(json) + self._CheckForTwitterError(data) return DirectMessage.NewFromJsonDict(data) def CreateFriendship(self, user): @@ -1210,6 +1719,7 @@ url = 'http://twitter.com/friendships/create/%s.json' % user json = self._FetchUrl(url, post_data={}) data = simplejson.loads(json) + self._CheckForTwitterError(data) return User.NewFromJsonDict(data) def DestroyFriendship(self, user): @@ -1225,6 +1735,55 @@ url = 'http://twitter.com/friendships/destroy/%s.json' % user json = self._FetchUrl(url, post_data={}) data = simplejson.loads(json) + self._CheckForTwitterError(data) + return User.NewFromJsonDict(data) + + def CreateFavorite(self, status): + '''Favorites the status specified in the status parameter as the authenticating user. + Returns the favorite status when successful. + + The twitter.Api instance must be authenticated. + + Args: + The twitter.Status instance to mark as a favorite. + Returns: + A twitter.Status instance representing the newly-marked favorite. + ''' + url = 'http://twitter.com/favorites/create/%s.json' % status.id + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def DestroyFavorite(self, status): + '''Un-favorites the status specified in the ID parameter as the authenticating user. + Returns the un-favorited status in the requested format when successful. + + The twitter.Api instance must be authenticated. + + Args: + The twitter.Status to unmark as a favorite. + Returns: + A twitter.Status instance representing the newly-unmarked favorite. + ''' + url = 'http://twitter.com/favorites/destroy/%s.json' % status.id + json = self._FetchUrl(url, post_data={}) + data = simplejson.loads(json) + self._CheckForTwitterError(data) + return Status.NewFromJsonDict(data) + + def GetUserByEmail(self, email): + '''Returns a single user by email address. + + Args: + email: The email of the user to retrieve. + Returns: + A twitter.User instance representing that user + ''' + url = 'http://twitter.com/users/show.json?email=%s' % email + json = self._FetchUrl(url) + data = simplejson.loads(json) + self._CheckForTwitterError(data) return User.NewFromJsonDict(data) def SetCredentials(self, username, password): @@ -1293,6 +1852,20 @@ self._request_headers['X-Twitter-Client-URL'] = url self._request_headers['X-Twitter-Client-Version'] = version + def SetSource(self, source): + '''Suggest the "from source" value to be displayed on the Twitter web site. + + The value of the 'source' parameter must be first recognized by + the Twitter server. New source values are authorized on a case by + case basis by the Twitter development team. + + Args: + source: + The source name as a string. Will be sent to the server as + the 'source' parameter. + ''' + self._default_params['source'] = source + def _BuildUrl(self, url, path_elements=None, extra_params=None): # Break url into consituent parts (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) @@ -1325,9 +1898,12 @@ def _InitializeUserAgent(self): user_agent = 'Python-urllib/%s (python-twitter/%s)' % \ - (self._urllib.__version__, twitter.__version__) + (self._urllib.__version__, __version__) self.SetUserAgent(user_agent) + def _InitializeDefaultParameters(self): + self._default_params = {} + def _AddAuthorizationHeader(self, username, password): if username and password: basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1] @@ -1390,27 +1966,47 @@ else: return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()])) + def _CheckForTwitterError(self, data): + """Raises a TwitterError if twitter returns an error message. + + Args: + data: A python dict created from the Twitter json response + Raises: + TwitterError wrapping the twitter error message if one exists. + """ + # Twitter errors are relatively unlikely, so it is faster + # to check first, rather than try and catch the exception + if 'error' in data: + raise TwitterError(data['error']) + def _FetchUrl(self, url, post_data=None, parameters=None, no_cache=None): - """Fetch a URL, optionally caching for a specified time. + '''Fetch a URL, optionally caching for a specified time. Args: url: The URL to retrieve - data: A dict of (str, unicode) key value pairs. If set, POST will be used. - parameters: A dict of key/value pairs that should added to - the query string. [OPTIONAL] - username: A HTTP Basic Auth username for this request - username: A HTTP Basic Auth password for this request + post_data: + A dict of (str, unicode) key/value pairs. If set, POST will be used. + parameters: + A dict whose key/value pairs should encoded and added + to the query string. [OPTIONAL] no_cache: If true, overrides the cache on the current request Returns: A string containing the body of the response. - """ + ''' + # Build the extra parameters dict + extra_params = {} + if self._default_params: + extra_params.update(self._default_params) + if parameters: + extra_params.update(parameters) + # Add key/value parameters to the query string of the url - url = self._BuildUrl(url, extra_params=parameters) + url = self._BuildUrl(url, extra_params=extra_params) # Get a url opener that can handle basic auth opener = self._GetOpener(url, username=self._username, password=self._password) @@ -1420,6 +2016,7 @@ # Open and return the URL immediately if we're not going to cache if encoded_post_data or no_cache or not self._cache or not self._cache_timeout: url_data = opener.open(url, encoded_post_data).read() + opener.close() else: # Unique keys are a combination of the url and the username if self._username: @@ -1433,6 +2030,7 @@ # If the cached version is outdated then fetch another and store it if not last_cached or time.time() >= last_cached + self._cache_timeout: url_data = opener.open(url, encoded_post_data).read() + opener.close() self._cache.Set(key, url_data) else: url_data = self._cache.Get(key) @@ -1440,6 +2038,7 @@ # Always return the latest version return url_data + class _FileCacheError(Exception): '''Base exception class for FileCache related errors''' @@ -1492,11 +2091,14 @@ def _GetUsername(self): '''Attempt to find the username in a cross-platform fashion.''' - return os.getenv('USER') or \ - os.getenv('LOGNAME') or \ - os.getenv('USERNAME') or \ - os.getlogin() or \ - 'nobody' + try: + return os.getenv('USER') or \ + os.getenv('LOGNAME') or \ + os.getenv('USERNAME') or \ + os.getlogin() or \ + 'nobody' + except (IOError, OSError), e: + return 'nobody' def _GetTmpCachePath(self): username = self._GetUsername() @@ -1515,7 +2117,11 @@ self._root_directory = root_directory def _GetPath(self,key): - hashed_key = md5.new(key).hexdigest() + try: + hashed_key = md5(key).hexdigest() + except TypeError: + hashed_key = md5.new(key).hexdigest() + return os.path.join(self._root_directory, self._GetPrefix(hashed_key), hashed_key) diff -Nru /tmp/mc1gZvnRXF/python-twitter-0.5/twitter_test.py /tmp/7opw10sJTP/python-twitter-0.6/twitter_test.py --- python-twitter-0.5/twitter_test.py 2007-07-15 16:26:27.000000000 +0100 +++ python-twitter-0.6/twitter_test.py 2009-03-31 16:51:05.000000000 +0100 @@ -1,8 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/python2.4 # -*- coding: utf-8 -*-# - # # Copyright 2007 Google Inc. All Rights Reserved. +# +# 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. '''Unit tests for the twitter.py library''' @@ -11,13 +22,14 @@ import os import simplejson import time +import calendar import unittest import twitter class StatusTest(unittest.TestCase): - SAMPLE_JSON = '''{"created_at": "Fri Jan 26 23:17:14 +0000 2007", "id": 4391023, "text": "A l\u00e9gp\u00e1rn\u00e1s haj\u00f3m tele van angoln\u00e1kkal.", "user": {"description": "Canvas. JC Penny. Three ninety-eight.", "id": 718443, "location": "Okinawa, Japan", "name": "Kesuke Miyagi", "profile_image_url": "http:\/\/twitter.com\/system\/user\/profile_image\/718443\/normal\/kesuke.png", "screen_name": "kesuke", "url": "http:\/\/twitter.com\/kesuke"}}''' + SAMPLE_JSON = '''{"created_at": "Fri Jan 26 23:17:14 +0000 2007", "id": 4391023, "text": "A l\u00e9gp\u00e1rn\u00e1s haj\u00f3m tele van angoln\u00e1kkal.", "user": {"description": "Canvas. JC Penny. Three ninety-eight.", "id": 718443, "location": "Okinawa, Japan", "name": "Kesuke Miyagi", "profile_image_url": "http://twitter.com/system/user/profile_image/718443/normal/kesuke.png", "screen_name": "kesuke", "url": "http://twitter.com/kesuke"}}''' def _GetSampleUser(self): return twitter.User(id=718443, @@ -48,7 +60,7 @@ status = twitter.Status() status.SetId(4391023) self.assertEqual(4391023, status.GetId()) - created_at = time.mktime((2007, 1, 26, 23, 17, 14, -1, -1, -1)) + created_at = calendar.timegm((2007, 1, 26, 23, 17, 14, -1, -1, -1)) status.SetCreatedAt('Fri Jan 26 23:17:14 +0000 2007') self.assertEqual('Fri Jan 26 23:17:14 +0000 2007', status.GetCreatedAt()) self.assertEqual(created_at, status.GetCreatedAtInSeconds()) @@ -65,7 +77,7 @@ status = twitter.Status() status.id = 1 self.assertEqual(1, status.id) - created_at = time.mktime((2007, 1, 26, 23, 17, 14, -1, -1, -1)) + created_at = calendar.timegm((2007, 1, 26, 23, 17, 14, -1, -1, -1)) status.created_at = 'Fri Jan 26 23:17:14 +0000 2007' self.assertEqual('Fri Jan 26 23:17:14 +0000 2007', status.created_at) self.assertEqual(created_at, status.created_at_in_seconds) @@ -75,7 +87,7 @@ self.assertEqual(718443, status.user.id) def _ParseDate(self, string): - return time.mktime(time.strptime(string, '%b %d %H:%M:%S %Y')) + return calendar.timegm(time.strptime(string, '%b %d %H:%M:%S %Y')) def testRelativeCreatedAt(self): '''Test various permutations of Status relative_created_at''' @@ -146,7 +158,7 @@ class UserTest(unittest.TestCase): - SAMPLE_JSON = '''{"description": "Indeterminate things", "id": 673483, "location": "San Francisco, CA", "name": "DeWitt", "profile_image_url": "http:\/\/twitter.com\/system\/user\/profile_image\/673483\/normal\/me.jpg", "screen_name": "dewitt", "status": {"created_at": "Fri Jan 26 17:28:19 +0000 2007", "id": 4212713, "text": "\\"Select all\\" and archive your Gmail inbox. The page loads so much faster!"}, "url": "http:\/\/unto.net\/"}''' + SAMPLE_JSON = '''{"description": "Indeterminate things", "id": 673483, "location": "San Francisco, CA", "name": "DeWitt", "profile_image_url": "http://twitter.com/system/user/profile_image/673483/normal/me.jpg", "screen_name": "dewitt", "status": {"created_at": "Fri Jan 26 17:28:19 +0000 2007", "id": 4212713, "text": "\\"Select all\\" and archive your Gmail inbox. The page loads so much faster!"}, "url": "http://unto.net/"}''' def _GetSampleStatus(self): return twitter.Status(created_at='Fri Jan 26 17:28:19 +0000 2007', @@ -307,6 +319,19 @@ api.SetUrllib(self._urllib) self._api = api + def testTwitterError(self): + '''Test that twitter responses containing an error message are wrapped.''' + self._AddHandler('http://twitter.com/statuses/public_timeline.json', + curry(self._OpenTestData, 'public_timeline_error.json')) + # Manually try/catch so we can check the exception's value + try: + statuses = self._api.GetPublicTimeline() + except twitter.TwitterError, error: + # If the error message matches, the test passes + self.assertEqual('test error', error.message) + else: + self.fail('TwitterError expected') + def testGetPublicTimeline(self): '''Test the twitter.Api GetPublicTimeline method''' self._AddHandler('http://twitter.com/statuses/public_timeline.json?since_id=12345', @@ -359,24 +384,24 @@ def testGetReplies(self): '''Test the twitter.Api GetReplies method''' - self._AddHandler('http://twitter.com/statuses/replies.json', + self._AddHandler('http://twitter.com/statuses/replies.json?page=1', curry(self._OpenTestData, 'replies.json')) - statuses = self._api.GetReplies() + statuses = self._api.GetReplies(page=1) self.assertEqual(36657062, statuses[0].id) def testGetFriends(self): '''Test the twitter.Api GetFriends method''' - self._AddHandler('http://twitter.com/statuses/friends.json', + self._AddHandler('http://twitter.com/statuses/friends.json?page=1', curry(self._OpenTestData, 'friends.json')) - users = self._api.GetFriends() + users = self._api.GetFriends(page=1) buzz = [u.status for u in users if u.screen_name == 'buzz'] self.assertEqual(89543882, buzz[0].id) def testGetFollowers(self): '''Test the twitter.Api GetFollowers method''' - self._AddHandler('http://twitter.com/statuses/followers.json', + self._AddHandler('http://twitter.com/statuses/followers.json?page=1', curry(self._OpenTestData, 'followers.json')) - users = self._api.GetFollowers() + users = self._api.GetFollowers(page=1) # This is rather arbitrary, but spot checking is better than nothing alexkingorg = [u.status for u in users if u.screen_name == 'alexkingorg'] self.assertEqual(89554432, alexkingorg[0].id) @@ -392,9 +417,9 @@ def testGetDirectMessages(self): '''Test the twitter.Api GetDirectMessages method''' - self._AddHandler('http://twitter.com/direct_messages.json', + self._AddHandler('http://twitter.com/direct_messages.json?page=1', curry(self._OpenTestData, 'direct_messages.json')) - statuses = self._api.GetDirectMessages() + statuses = self._api.GetDirectMessages(page=1) self.assertEqual(u'A légpárnás hajóm tele van angolnákkal.', statuses[0].text) def testPostDirectMessage(self): @@ -466,13 +491,22 @@ def __init__(self, handlers): self._handlers = handlers + self._opened = False def open(self, url, data=None): + if self._opened: + raise Exception('MockOpener already opened.') if url in self._handlers: + self._opened = True return self._handlers[url]() else: raise Exception('Unexpected URL %s' % url) + def close(self): + if not self._opened: + raise Exception('MockOpener closed before it was opened.') + self._opened = False + class MockHTTPBasicAuthHandler(object): '''A mock replacement for HTTPBasicAuthHandler'''