Skip to content
Browse files

Initial copy of the hg tip - no history yet, that will come later

  • Loading branch information...
1 parent 5632624 commit dbe768ad09a40a77c42a0d2c63809d71355a0033 @bear committed Sep 12, 2012
View
508 CHANGES
@@ -0,0 +1,508 @@
+2011-12-03
+
+ https://code.google.com/p/python-twitter/source/detail?r=263fe2a0db8be23347e92b81d6ab3c33b4ef292f
+ Comment by qfuxiang to the above changeset
+ The base url was wrong for the Followers API calls
+
+ https://code.google.com/p/python-twitter/issues/detail?id=213
+ Add include_entities parameter to GetStatus()
+ Patch by gaelenh
+
+ https://code.google.com/p/python-twitter/issues/detail?id=214
+ Change PostUpdate() so that it takes the shortened link into
+ account. Small tweak to the patch provided to make the
+ shortened-link length set by a API value instead of a constant.
+ Patch by ceesjan.ytec
+
+ https://code.google.com/p/python-twitter/issues/detail?id=216
+ AttributeError handles the fact that win* doesn't implement
+ os.getlogin()
+ Patch by yaleman
+
+ https://code.google.com/p/python-twitter/issues/detail?id=217
+ As described at https://dev.twitter.com/docs/api/1/get/trends,
+ GET trends (corresponding to Api.GetTrendsCurrent) is now
+ deprecated in favor of GET trends/:woeid. GET trends also now
+ requires authentication, while trends/:woeid doesn't.
+ Patch and excellent description by jessica.mckellar
+
+ https://code.google.com/p/python-twitter/issues/detail?id=218
+ Currently, two Trends containing the same information
+ (name, query, and timestamp) aren't considered equal because
+ __eq__ isn't overridden, like it is for Status, User, and the
+ other Twitter objects.
+ Patch and excellent description by jessica.mckellar
+
+ https://code.google.com/p/python-twitter/issues/detail?id=220
+ https://code.google.com/p/python-twitter/issues/detail?id=211
+ https://code.google.com/p/python-twitter/issues/detail?id=206
+ All variations on a theme - basically Twitter is returning
+ something different for an error payload. Changed code to
+ check for both 'error' and 'errors'.
+
+2011-05-08
+
+ https://code.google.com/p/python-twitter/issues/detail?id=184
+ A comment in this issue made me realize that the parameter sanity
+ check for max_id was missing in GetMentions() - added
+
+ First pass at working in some of the cursor support that has been
+ in the Twitter API but we haven't made full use of - still working
+ out the small issues.
+
+2011-04-16
+
+ bumped version to 0.8.3
+ released 0.8.2 to PyPI
+ bumped version to 0.8.2
+
+ Issue 193
+ http://code.google.com/p/python-twitter/issues/detail?id=193
+ Missing retweet_count field on Status object
+ Patch (with minor tweaks) by from alissonp
+
+ Issue 181
+ http://code.google.com/p/python-twitter/issues/detail?id=181
+ Add oauth2 to install_requires parameter list and also updated
+ README to note that the oauth2 lib can be found in two locations
+
+ Issue 182, Issue 137, Issue 93, Issue 190
+ language value missing from User object
+ Added 'lang' item and also some others that were needed:
+ verified, notifications, contributors_enabled and listed_count
+ patches by wreinerat, apetresc, jpwigan and ghills
+
+2011-02-26
+
+ Issue 166
+ http://code.google.com/p/python-twitter/issues/detail?id=166
+ Added a basic, but sadly needed, check when parsing the json
+ returned by Twitter as Twitter has a habit of returning the
+ failwhale HTML page for a json api call :(
+ Patch (with minor tweaks) by adam.aviv
+
+ Issue 187
+ http://code.google.com/p/python-twitter/issues/detail?id=187
+ Applied patch by edward.hades to fix issue where MaximumHitFrequency
+ returns 0 when requests are maxed out
+
+ Issue 184
+ http://code.google.com/p/python-twitter/issues/detail?id=184
+ Applied patch by jmstaley to put into the GetUserTimeline API
+ parameter list the max_id value (it was being completely ignored)
+
+2011-02-20
+
+ Added retweeted to Status class
+ Fixed Status class to return Hashtags list in AsDict() call
+
+ Issue 185
+ http://code.google.com/p/python-twitter/issues/detail?id=185
+ Added retweeted_status to Status class - patch by edward.hades
+
+ Issue 183
+ http://code.google.com/p/python-twitter/issues/detail?id=183
+ Removed errant print statement - reported by ProgVal
+
+2010-12-21
+
+ Setting version to 0.8.1
+
+ Issue 179
+ http://code.google.com/p/python-twitter/issues/detail?id=179
+ Added MANIFEST.in to give setup.py sdist some clues as to what
+ files to include in the tarball
+
+2010-11-14
+
+ Setting version to 0.8 for a bit as having a branch for this is
+ really overkill, i'll just take DeWitt advice and tag it when
+ the release is out the door
+
+ Issue 175
+ http://code.google.com/p/python-twitter/issues/detail?id=175
+ Added geo_enabled to User class - basic parts of patch provided
+ by adam.aviv with other bits added by me to allow it to pass tests
+
+ Issue 174
+ http://code.google.com/p/python-twitter/issues/detail?id=174
+ Added parts of adam.aviv's patch - the bits that add new field items
+ to the Status class.
+
+ Issue 159
+ http://code.google.com/p/python-twitter/issues/detail?id=159
+ Added patch form adam.aviv to make the term parameter for GetSearch()
+ optional if geocode parameter is supplied
+
+2010-11-03
+
+ Ran pydoc to generate docs
+
+2010-10-16
+
+ Fixed bad date in previous CHANGES entry
+
+ Fixed source of the python-oauth2 library we use: from brosner
+ to simplegeo
+
+ I made a pass thru the docstrings and updated many to be the
+ text from the current Twitter API docs. Also fixed numerous
+ whitespace issues and did a s/[optional]/[Optional]/ change.
+
+ Imported work by Colin Howe that he did to get the tests working.
+ http://code.google.com/r/colinthehowe-python-twitter-tests/source/detail?r=6cff589aca9c955df8354fe4d8e302ec4a2eb31c
+ http://code.google.com/r/colinthehowe-python-twitter-tests/source/detail?r=cab8e32d7a9c34c66d2e75eebc7a1ba6e1eac8ce
+ http://code.google.com/r/colinthehowe-python-twitter-tests/source/detail?r=b434d9e5dd7b989ae24483477e3f00b1ad362cc5
+
+ Issue 169
+ http://code.google.com/p/python-twitter/issues/detail?id=169
+ Patch by yaemog which adds missing Trends support.
+
+ Issue 168
+ http://code.google.com/p/python-twitter/issues/detail?id=168
+ Only cache successful results as suggested by yaemog.
+
+ Issue 111
+ http://code.google.com/p/python-twitter/issues/detail?id=111
+ Added a new GetUserRetweets() call as suggested by yash888
+ Patch given was adjusted to reflect the current code requirements.
+
+ Issue 110
+ Added a VerifyCredentials() sample call to the README example
+
+ Issue 105
+ Added support for the page parameter to GetFriendsTimeline()
+ as requested by jauderho.
+ I also updated GetFriendsTimeline() to follow the current
+ Twitter API documentation
+
+ Somewhere in the patch frenzy of today an extra GetStatus()
+ def was introduced!?! Luckily it was caught by the tests.
+ wooo tests! \m/
+
+ Setting version to 0.8
+
+ r0.8 branch created and trunk set to version 0.9-devel
+
+2010-09-26
+
+ Issue 150
+ http://code.google.com/p/python-twitter/issues/detail?id=150
+ Patch by blhobbes which removes a double quoting issue that
+ was happening for GetSearch()
+ Reported by huubhuubbarbatruuk
+
+ Issue 160
+ http://code.google.com/p/python-twitter/issues/detail?id=160
+ Patch by yaemog which adds support for include_rts and
+ include_entities support to GetUserTimeline and GetPublicTimeline
+ Small tweaks post-patch
+
+ Applied docstring tweak suggested by dclinton in revision comment
+ http://code.google.com/p/python-twitter/source/detail?r=a858412e38f7e3856fef924291ef039284d3a6e1
+ Thanks for the catch!
+
+ Issue 164
+ http://code.google.com/p/python-twitter/issues/detail?id=164
+ Patch by yaemog which adds GetRetweets support.
+ Small tweaks and two typo fixes post-patch.
+
+ Issue 165
+ http://code.google.com/p/python-twitter/issues/detail?id=165
+ Patch by yaemog which adds GetStatus support.
+ Small tweaks post-patch
+
+ Issue 163
+ http://code.google.com/p/python-twitter/issues/detail?id=163
+ Patch by yaemog which adds users/lookup support.
+ Small tweaks to docstring only post-patch.
+
+ Changed username/password parameter to Api class to be
+ consumer_key/consumer_secret to better match the new
+ oAuth only world that Twitter has demanded.
+
+ Added debugHTTP to the parameter list to Api class to
+ control if/when the urllib debug output is displayed.
+
+2010-08-25
+
+ First pass at adding list support.
+ Added a new List class and also added to the Api class
+ new methods for working with lists:
+
+ CreateList(self, user, name, mode=None, description=None)
+ DestroyList(self, user, id)
+ CreateSubscription(self, owner, list)
+ DestroySubscription(self, owner, list)
+ GetSubscriptions(self, user, cursor=-1)
+ GetLists(self, user, cursor=-1)
+
+2010-08-24
+
+ Fixed introduced bug in the Destroy* and Create* API calls
+ where any of the routines were passing in an empty dict for
+ POST data. Before the oAuth change that was enough to tell
+ _FetchUrl() to use POST instead of GET but now a non-empty
+ dict is required.
+
+ Issue 144
+ http://code.google.com/p/python-twitter/issues/detail?id=144
+ GetFriends() where it was failing with a 'unicode object has
+ no attribute get'. This was caused when Twitter changed how
+ they return the JSON data. It used to be a straight list but
+ now there are some elements *and* then the list.
+
+2010-08-18
+
+ Applied the json/simplejson part of the patch found
+ in Issue 64 (http://code.google.com/p/python-twitter/issues/detail?id=64)
+ Patch provided by Thomas Bohmbach
+
+ Applied patch provided by liris.pp in Issue 147
+ http://code.google.com/p/python-twitter/issues/detail?id=147
+ Ensures that during a PostStatus we count the length using a unicode aware
+ len() routine. Tweaked patch slightly to take into account that the
+ twitter.Api() instance may have been setup with None for input_encoding.
+
+2010-08-17
+
+ Fixed error in the POST path for _FetchUrl() where by
+ I show to the world that yes, I do make last minute
+ changes and completely forget to test them :(
+ Thanks to Peter Sanchez for finding and pointing to
+ working code that showed the fix
+
+2010-08-15
+
+ Added more help text (I hope it helps) to the README
+ and also to get_access_token.py.
+
+ Added doctext notes to twitter.Api() parameter list
+ to explain more about oAuth.
+
+ Added import exception handling for parse_qs() and
+ parse_qsl() as it seems those funcitons moved between
+ 2.5 and 2.6 so the oAuth update broke the lib under
+ python2.5. Thanks to Rich for the bug find (sorry
+ it had to be found the hard way!)
+
+ from changeset 184:60315000989c by DeWitt
+ Update the generated twitter.py docs to match the trunk
+
+2010-08-14
+
+ Fixed silly typo in _FetchUrl() when doing a POST
+ Thanks to Peter Sanchez for the find and fix!
+
+ Added some really basic text to the get_access_token.py
+ startup output that explains why, for now, you need to
+ visit Twitter and get an Application key/secret to use
+ this library
+
+2010-08-12
+
+ Updated code to use python-oauth2 library for authentication.
+ Twitter has set a deadline, 2010-08-16 as of this change, for
+ the switch from Basic to oAuth.
+
+ The oAuth integration was inspired by the work done by
+ Hameedullah Khan and others.
+
+ The change to using python-oauth2 library was done purely to
+ align python-twitter with an oauth library that was maintained
+ and had tests to try and minimize grief moving forward.
+
+ Slipped into GetFriendsTimeline() a new parameter, retweets, to
+ allow the call to pull from the "friends_timeline" or the
+ "home_timeline".
+
+ Fixed some typos and white-space issues and also updated the
+ README to point to the new Twitter Dev site.
+
+2010-08-02
+
+ Updated copyright information.
+
+2010-06-13
+
+ Applied changeset from nicdumz repo nicdumz-cleaner-python-twitter
+ r=07df3feee06c8d0f9961596e5fceae9e74493d25
+ datetime is required for MaximumHitFrequency
+
+ Applied changeset from nicdumz repo nicdumz-cleaner-python-twitter
+ r=dd669dff32d101856ed6e50fe8bd938640b04d77
+ update source URLs in README
+
+ Applied changeset from nicdumz repo nicdumz-cleaner-python-twitter
+ r=8f0796d7fdcea17f4162aeb22d3c36cb603088c7
+ adjust tests to reflect http://twitter.com -> https://twitter.com change
+
+ Applied changeset from nicdumz repo nicdumz-cleaner-python-twitter
+ r=3c05b8ebe59eca226d9eaef2760cecca9d50944a
+ tests: add .info() method to objects returned by our Mockup handler
+ This is required to completely mimick urllib, and have successful
+ response.headers attribute accesses.
+
+ Applied partial patch for Issue 113
+ http://code.google.com/p/python-twitter/issues/detail?id=113
+
+ The partial bit means we changed the parameter from "page" to "cursor"
+ so the call would work. What was left out was a more direct way
+ to return the cursor value *after* the call and also in the patch
+ they also changed the method to return an iterator.
+
+2010-05-17
+
+ Issue 50 http://code.google.com/p/python-twitter/issues/detail?id=50
+ Applied patch by wheaties.box that implements a new method to return
+ the Rate Limit Status and also adds the new method MaximumHitFrequency
+
+ Multiple typo, indent and whitespace tweaks
+
+ Issue 60 http://code.google.com/p/python-twitter/issues/detail?id=60
+ Pulled out new GetFavorites and GetMentions methods from the patch
+ submitted by joegermuska
+
+ Issue 62 http://code.google.com/p/python-twitter/issues/detail?id=62
+ Applied patch from lukev123 that adds gzip compression to the GET
+ requests sent to Twitter. The patch was modified to default gzip to
+ False and to allow the twitter.API class instantiation to set the
+ value to True. This was done to not change current default
+ behaviour radically.
+
+ Issue 80 http://code.google.com/p/python-twitter/issues/detail?id=80
+ Fixed PostUpdate() call example in the README
+
+2010-05-16
+
+ Issue 19 http://code.google.com/p/python-twitter/issues/detail?id=19
+ TinyURL example and the idea for this comes from a bug filed by
+ acolorado with patch provided by ghills.
+
+ Issue 37 http://code.google.com/p/python-twitter/issues/detail?id=37
+ Added base_url to the twitter.API class init call to allow the user
+ to override the default https://twitter.com base. Since Twitter now
+ supports https for all calls I (bear) changed the patch to default to
+ https instead of http.
+ Original issue by kotecha.ravi, patch by wiennat and with implementation
+ tweaks by bear.
+
+ Issue 45 http://code.google.com/p/python-twitter/issues/detail?id=45
+ Two grammar fixes for relative_created_at property
+ Patches by thomasdyson and chris.boardman07
+
+2010-01-24
+
+ Applying patch submitted to fix Issue 70
+ http://code.google.com/p/python-twitter/issues/detail?id=70
+
+ The patch was originally submitted by user ghills, adapted by livibetter and
+ adapted even further by JimMoefoe (read the comments for the full details :) )
+
+ Applying patch submitted by markus.magnuson to add new method GetFriendIDs
+ Issue 94 http://code.google.com/p/python-twitter/issues/detail?id=94
+
+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.
+ Added back support for setuptools (conditionally)
+ Added support for X-Twitter-* HTTP headers
+ Fixed the tests to work across all timezones
+ Removed the 140 character limit from PostUpdate
+ Added support for per-user tmp cache directories
+
+2007-06-13
+
+ Released 0.4.
+ Fixed a unicode error that prevented tweet.py from working.
+ Added DestroyStatus
+ Added DestroyDirectMessage
+ Added CreateFriendship
+ Added DestoryFriendship
+
+2007-06-03
+
+ Fixed the bug that prevented unicode strings being posted
+ Username and password now set on twitter.Api, not individual method calls
+ Added SetCredentials and ClearCredentials
+ Added GetUser ("users/show" in the twitter web api)
+ Added GetFeatured
+ Added GetDirectMessages
+ Added GetStatus ("statuses/show" in the twitter web api)
+ Added GetReplies
+ Added optional since_id parameter on GetPublicTimeline
+ Added optional since parameter on GetUserTimeline
+ Added optional since and user parameters on GetFriendsTimeline
+ Added optional user parameter on GetFriends
+
+2007-04-27
+
+ Modified examples/twitter-to-xhtml.py to handle unicode
+ Dropped dependency on setuptools (too complicated/buggy)
+ Added unicode test cases
+ Fixed issue 2 "Rename needs an unlink in front"
+
+2007-04-02
+
+ Released 0.3.
+ Use gmtime not localtime to calculate relative_created_at.
+
+2007-03-26
+
+ Released 0.2
+ GetUserTimeline can accept userid or username.
+
+2007-03-21
+
+ Calculate relative_created_at on the fly
+
+2007-01-28
+
+ Released 0.1
+ Initial checkin of python-twitter
+
View
13 COPYING
@@ -0,0 +1,13 @@
+ Copyright 2007 The Python-Twitter Developers
+
+ 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.
View
202 LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
View
9 MANIFEST.in
@@ -0,0 +1,9 @@
+include CHANGES
+include COPYING
+include LICENSE
+include NOTICE
+include README
+include *.py
+recursive-include examples *.py
+recursive-include doc *.html
+prune .DS_Store
View
13 NOTICE
@@ -0,0 +1,13 @@
+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.
+
+The python-oauth2 library (http://github.com/simplegeo/python-oauth2) is used under the terms of the MIT license and is copyright Leah Culver.
+See http://github.com/simplegeo/python-oauth2/blob/master/LICENSE.txt for details.
+
+The httplib2 library (http://code.google.com/p/httplib2) is used under the terms of the MIT license and is copyright Joe Gregorio.
+See http://code.google.com/p/httplib2/source/browse/python2/httplib2/__init__.py for details.
+
+This code is made available under the Apache License and is copyright the Python-Twitter Developers.
+
View
208 README.md
@@ -1,2 +1,206 @@
-python-twitter
-==============
+=Python Twitter=
+
+_A Python wrapper around the Twitter API_
+
+Author: `The Python-Twitter Developers <python-twitter@googlegroups.com>`
+
+==Introduction==
+
+This library provides a pure Python interface for the Twitter API.
+
+Twitter (http://twitter.com) provides a service that allows people to
+connect via the web, IM, and SMS. Twitter exposes a web services API
+(http://dev.twitter.com/doc) and this library is intended to make
+it even easier for Python programmers to use.
+
+==Building==
+
+*From source:*
+
+Install the dependencies:
+
+ SimpleJson
+ http://cheeseshop.python.org/pypi/simplejson
+
+ SimpleGeo's OAuth2
+ http://github.com/simplegeo/python-oauth2 or
+ http://pypi.python.org/pypi/oauth2
+
+ HTTPLib2 (installed along with oauth2 if you use setuptools)
+ http://code.google.com/p/httplib2/
+
+Download the latest python-twitter library from:
+
+ http://code.google.com/p/python-twitter/
+
+Extract the source distribution and run:
+
+{{{
+ $ python setup.py build
+ $ python setup.py install
+}}}
+
+*Testing*
+
+With setuptools installed:
+
+{{{
+ $ python setup.py test
+}}}
+
+Without setuptools installed:
+
+{{{
+ $ python twitter_test.py
+}}}
+
+==Getting the code==
+
+View the trunk at:
+
+ http://code.google.com/p/python-twitter/source/
+
+Check out the latest development version anonymously with:
+
+{{{
+ $ hg clone http://python-twitter.googlecode.com/hg/ python-twitter
+ $ cd python-twitter
+ $ hg update dev
+}}}
+
+==Documentation==
+
+View the last release API documentation at:
+
+ http://dev.twitter.com/doc
+
+==Using==
+
+The library provides a Python wrapper around the Twitter API and
+the Twitter data model.
+
+*Model:*
+
+The three model classes are twitter.Status, twitter.User, and
+twitter.DirectMessage. The API methods return instances of these
+classes.
+
+To read the full API for twitter.Status, twitter.User, or
+twitter.DirectMessage, run:
+
+{{{
+ $ pydoc twitter.Status
+ $ pydoc twitter.User
+ $ pydoc twitter.DirectMessage
+}}}
+
+*API:*
+
+The API is exposed via the twitter.Api class.
+
+To create an instance of the twitter.Api class:
+
+{{{
+ >>> import twitter
+ >>> api = twitter.Api()
+}}}
+
+To create an instance of the twitter.Api with login credentials (many API
+calls required the client to be authenticated.)
+
+The python-twitter library now only supports oAuth authentication as the
+Twitter devs have indicated that oAuth is the only method that will be
+supported moving forward.
+
+ >>> api = twitter.Api(consumer_key='consumer_key',
+ consumer_secret='consumer_secret',
+ access_token_key='access_token',
+ access_token_secret='access_token_secret')
+
+To see if your credentials are successful:
+ NOTE - much more than the small sample given here will print
+
+ >>> print api.VerifyCredentials()
+ {"id": 16133, "location": "Philadelphia", "name": "bear"}
+
+To fetch the most recently posted public Twitter status messages:
+
+{{{
+ >>> statuses = api.GetPublicTimeline()
+ >>> print [s.user.name for s in statuses]
+ [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone']
+}}}
+
+To fetch a single user's public status messages, where "user" is either
+a Twitter "short name" or their user id.
+
+{{{
+ >>> statuses = api.GetUserTimeline(user)
+ >>> print [s.text for s in statuses]
+}}}
+
+To fetch a list a user's friends (requires authentication):
+
+{{{
+ >>> users = api.GetFriends()
+ >>> print [u.name for u in users]
+}}}
+
+To post a Twitter status message (requires authentication):
+
+{{{
+ >>> status = api.PostUpdate('I love python-twitter!')
+ >>> print status.text
+ I love python-twitter!
+}}}
+
+There are many more API methods, to read the full API documentation:
+
+{{{
+ $ pydoc twitter.Api
+}}}
+
+==Todo==
+
+Patches and bug reports are welcome, just please keep the style
+consistent with the original source.
+
+Add more example scripts.
+
+The twitter.Status and twitter.User classes are going to be hard
+to keep in sync with the API if the API changes. More of the
+code could probably be written with introspection.
+
+Statement coverage of twitter_test is only about 80% of twitter.py.
+
+The twitter.Status and twitter.User classes could perform more
+validation on the property setters.
+
+==More Information==
+
+Please visit http://groups.google.com/group/python-twitter for more discussion.
+
+==Contributors==
+
+Additional thanks to Pierre-Jean Coudert, Omar Kilani, Jodok Batlogg,
+edleaf, glen.tregoning, Brad Choate, Jim Cortez, Jason Lemoine, Thomas
+Dyson, Robert Laquey, Hameedullah Khan, Mike Taylor, DeWitt Clinton,
+and the rest of the python-twitter mailing list.
+
+==License==
+
+{{{
+ Copyright 2007 The Python-Twitter Developers
+
+ 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.
+}}}
View
2,026 doc/twitter.html
2,026 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
71 examples/shorten_url.py
@@ -0,0 +1,71 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2007 The Python-Twitter Developers
+#
+# 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 class that defines the default URL Shortener.
+
+TinyURL is provided as the default and as an example.
+'''
+
+import urllib
+
+
+ # Change History
+ #
+ # 2010-05-16
+ # TinyURL example and the idea for this comes from a bug filed by
+ # acolorado with patch provided by ghills. Class implementation
+ # was done by bear.
+ #
+ # Issue 19 http://code.google.com/p/python-twitter/issues/detail?id=19
+ #
+
+
+class ShortenURL(object):
+ '''Helper class to make URL Shortener calls if/when required'''
+ def __init__(self,
+ userid=None,
+ password=None):
+ '''Instantiate a new ShortenURL object
+
+ Args:
+ userid: userid for any required authorization call [optional]
+ password: password for any required authorization call [optional]
+ '''
+ self.userid = userid
+ self.password = password
+
+ def Shorten(self,
+ longURL):
+ '''Call TinyURL API and returned shortened URL result
+
+ Args:
+ longURL: URL string to shorten
+
+ Returns:
+ The shortened URL as a string
+
+ Note:
+ longURL is required and no checks are made to ensure completeness
+ '''
+
+ result = None
+ f = urllib.urlopen("http://tinyurl.com/api-create.php?url=%s" % longURL)
+ try:
+ result = f.read()
+ finally:
+ f.close()
+
+ return result
View
141 examples/tweet.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python2.4
+
+'''Post a message to twitter'''
+
+__author__ = 'dewitt@google.com'
+
+import ConfigParser
+import getopt
+import os
+import sys
+import twitter
+
+
+USAGE = '''Usage: tweet [options] message
+
+ This script posts a message to Twitter.
+
+ Options:
+
+ -h --help : print this help
+ --consumer-key : the twitter consumer key
+ --consumer-secret : the twitter consumer secret
+ --access-key : the twitter access token key
+ --access-secret : the twitter access token secret
+ --encoding : the character set encoding used in input strings, e.g. "utf-8". [optional]
+
+ Documentation:
+
+ If either of the command line flags are not present, the environment
+ variables TWEETUSERNAME and TWEETPASSWORD will then be checked for your
+ consumer_key or consumer_secret, respectively.
+
+ If neither the command line flags nor the enviroment variables are
+ present, the .tweetrc file, if it exists, can be used to set the
+ default consumer_key and consumer_secret. The file should contain the
+ following three lines, replacing *consumer_key* with your consumer key, and
+ *consumer_secret* with your consumer secret:
+
+ A skeletal .tweetrc file:
+
+ [Tweet]
+ consumer_key: *consumer_key*
+ consumer_secret: *consumer_password*
+ access_key: *access_key*
+ access_secret: *access_password*
+
+'''
+
+def PrintUsageAndExit():
+ print USAGE
+ sys.exit(2)
+
+def GetConsumerKeyEnv():
+ return os.environ.get("TWEETUSERNAME", None)
+
+def GetConsumerSecretEnv():
+ return os.environ.get("TWEETPASSWORD", None)
+
+def GetAccessKeyEnv():
+ return os.environ.get("TWEETACCESSKEY", None)
+
+def GetAccessSecretEnv():
+ return os.environ.get("TWEETACCESSSECRET", None)
+
+class TweetRc(object):
+ def __init__(self):
+ self._config = None
+
+ def GetConsumerKey(self):
+ return self._GetOption('consumer_key')
+
+ def GetConsumerSecret(self):
+ return self._GetOption('consumer_secret')
+
+ def GetAccessKey(self):
+ return self._GetOption('access_key')
+
+ def GetAccessSecret(self):
+ return self._GetOption('access_secret')
+
+ def _GetOption(self, option):
+ try:
+ return self._GetConfig().get('Tweet', option)
+ except:
+ return None
+
+ def _GetConfig(self):
+ if not self._config:
+ self._config = ConfigParser.ConfigParser()
+ self._config.read(os.path.expanduser('~/.tweetrc'))
+ return self._config
+
+def main():
+ try:
+ shortflags = 'h'
+ longflags = ['help', 'consumer-key=', 'consumer-secret=',
+ 'access-key=', 'access-secret=', 'encoding=']
+ opts, args = getopt.gnu_getopt(sys.argv[1:], shortflags, longflags)
+ except getopt.GetoptError:
+ PrintUsageAndExit()
+ consumer_keyflag = None
+ consumer_secretflag = None
+ access_keyflag = None
+ access_secretflag = None
+ encoding = None
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ PrintUsageAndExit()
+ if o in ("--consumer-key"):
+ consumer_keyflag = a
+ if o in ("--consumer-secret"):
+ consumer_secretflag = a
+ if o in ("--access-key"):
+ access_keyflag = a
+ if o in ("--access-secret"):
+ access_secretflag = a
+ if o in ("--encoding"):
+ encoding = a
+ message = ' '.join(args)
+ if not message:
+ PrintUsageAndExit()
+ rc = TweetRc()
+ consumer_key = consumer_keyflag or GetConsumerKeyEnv() or rc.GetConsumerKey()
+ consumer_secret = consumer_secretflag or GetConsumerSecretEnv() or rc.GetConsumerSecret()
+ access_key = access_keyflag or GetAccessKeyEnv() or rc.GetAccessKey()
+ access_secret = access_secretflag or GetAccessSecretEnv() or rc.GetAccessSecret()
+ if not consumer_key or not consumer_secret or not access_key or not access_secret:
+ PrintUsageAndExit()
+ api = twitter.Api(consumer_key=consumer_key, consumer_secret=consumer_secret,
+ access_token_key=access_key, access_token_secret=access_secret,
+ input_encoding=encoding)
+ try:
+ 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 --encoding flag"
+ sys.exit(2)
+ print "%s just posted: %s" % (status.user.name, status.text)
+
+if __name__ == "__main__":
+ main()
View
69 examples/twitter-to-xhtml.py
@@ -0,0 +1,69 @@
+#!/usr/bin/python2.4
+
+'''Load the latest update for a Twitter user and leave it in an XHTML fragment'''
+
+__author__ = 'dewitt@google.com'
+
+import codecs
+import getopt
+import sys
+import twitter
+
+TEMPLATE = """
+<div class="twitter">
+ <span class="twitter-user"><a href="http://twitter.com/%s">Twitter</a>: </span>
+ <span class="twitter-text">%s</span>
+ <span class="twitter-relative-created-at"><a href="http://twitter.com/%s/statuses/%s">Posted %s</a></span>
+</div>
+"""
+
+def Usage():
+ print 'Usage: %s [options] twitterid' % __file__
+ print
+ print ' This script fetches a users latest twitter update and stores'
+ print ' the result in a file as an XHTML fragment'
+ print
+ print ' Options:'
+ print ' --help -h : print this help'
+ print ' --output : the output file [default: stdout]'
+
+
+def FetchTwitter(user, output):
+ assert user
+ statuses = twitter.Api().GetUserTimeline(user=user, count=1)
+ s = statuses[0]
+ xhtml = TEMPLATE % (s.user.screen_name, s.text, s.user.screen_name, s.id, s.relative_created_at)
+ if output:
+ Save(xhtml, output)
+ else:
+ print xhtml
+
+
+def Save(xhtml, output):
+ out = codecs.open(output, mode='w', encoding='ascii',
+ errors='xmlcharrefreplace')
+ out.write(xhtml)
+ out.close()
+
+def main():
+ try:
+ opts, args = getopt.gnu_getopt(sys.argv[1:], 'ho', ['help', 'output='])
+ except getopt.GetoptError:
+ Usage()
+ sys.exit(2)
+ try:
+ user = args[0]
+ except:
+ Usage()
+ sys.exit(2)
+ output = None
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ Usage()
+ sys.exit(2)
+ if o in ("-o", "--output"):
+ output = a
+ FetchTwitter(user, output)
+
+if __name__ == "__main__":
+ main()
View
91 get_access_token.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2007 The Python-Twitter Developers
+#
+# 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.
+
+
+import os
+import sys
+
+# parse_qsl moved to urlparse module in v2.6
+try:
+ from urlparse import parse_qsl
+except:
+ from cgi import parse_qsl
+
+import oauth2 as oauth
+
+REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
+ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
+AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
+SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
+
+consumer_key = "g00szXVinvEk7B9vD1vUQ"
+consumer_secret = "aL1ghD3Ki4MY1k1aEoLVeUVzoKmq7uRKX16GIiw"
+
+
+if consumer_key is None or consumer_secret is None:
+ print 'You need to edit this script and provide values for the'
+ print 'consumer_key and also consumer_secret.'
+ print ''
+ print 'The values you need come from Twitter - you need to register'
+ print 'as a developer your "application". This is needed only until'
+ print 'Twitter finishes the idea they have of a way to allow open-source'
+ print 'based libraries to have a token that can be used to generate a'
+ print 'one-time use key that will allow the library to make the request'
+ print 'on your behalf.'
+ print ''
+ sys.exit(1)
+
+signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1()
+oauth_consumer = oauth.Consumer(key=consumer_key, secret=consumer_secret)
+oauth_client = oauth.Client(oauth_consumer)
+
+print 'Requesting temp token from Twitter'
+
+resp, content = oauth_client.request(REQUEST_TOKEN_URL, 'GET')
+
+if resp['status'] != '200':
+ print 'Invalid respond from Twitter requesting temp token: %s' % resp['status']
+else:
+ request_token = dict(parse_qsl(content))
+
+ print ''
+ print 'Please visit this Twitter page and retrieve the pincode to be used'
+ print 'in the next step to obtaining an Authentication Token:'
+ print ''
+ print '%s?oauth_token=%s' % (AUTHORIZATION_URL, request_token['oauth_token'])
+ print ''
+
+ pincode = raw_input('Pincode? ')
+
+ token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
+ token.set_verifier(pincode)
+
+ print ''
+ print 'Generating and signing request for an access token'
+ print ''
+
+ oauth_client = oauth.Client(oauth_consumer, token)
+ resp, content = oauth_client.request(ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % pincode)
+ access_token = dict(parse_qsl(content))
+
+ if resp['status'] != '200':
+ print 'The request for a Token did not succeed: %s' % resp['status']
+ print access_token
+ else:
+ print 'Your Twitter Access Token key: %s' % access_token['oauth_token']
+ print ' Access Token secret: %s' % access_token['oauth_token_secret']
+ print ''
+
View
50 python-twitter.spec
@@ -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.7-devel
+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 <me@stevemilner.org> - 0.5-1
+- Initial package.
View
73 setup.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2007 The Python-Twitter Developers
+#
+# 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__ = 'python-twitter@googlegroups.com'
+__version__ = '0.8.3'
+
+
+# The base package metadata to be used by both distutils and setuptools
+METADATA = dict(
+ name = "python-twitter",
+ version = __version__,
+ py_modules = ['twitter'],
+ author='The Python-Twitter Developers',
+ author_email='python-twitter@googlegroups.com',
+ description='A python wrapper around the Twitter API',
+ license='Apache License 2.0',
+ url='http://code.google.com/p/python-twitter/',
+ keywords='twitter api',
+)
+
+# Extra package metadata to be used only if setuptools is installed
+SETUPTOOLS_METADATA = dict(
+ install_requires = ['setuptools', 'simplejson', 'oauth2'],
+ include_package_data = True,
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: Communications :: Chat',
+ 'Topic :: Internet',
+ ],
+ test_suite = 'twitter_test.suite',
+)
+
+
+def Read(file):
+ return open(file).read()
+
+def BuildLongDescription():
+ return '\n'.join([Read('README'), Read('CHANGES')])
+
+def Main():
+ # Build the long_description from the README and CHANGES
+ METADATA['long_description'] = BuildLongDescription()
+
+ # Use setuptools if available, otherwise fallback and use distutils
+ try:
+ import setuptools
+ METADATA.update(SETUPTOOLS_METADATA)
+ setuptools.setup(**METADATA)
+ except ImportError:
+ import distutils.core
+ distutils.core.setup(**METADATA)
+
+
+if __name__ == '__main__':
+ Main()
View
316 simplejson/__init__.py
@@ -0,0 +1,316 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> 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)
View
2,265 simplejson/_speedups.c
@@ -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;
+ }