Revert "Upgrade oauth2client to v4.1.3" am: 8f8bf75ceb am: f573761f1b
am: c2b62e4e86

Change-Id: I025429709dd0aa5070851e60ed7e829bbbcc958d
diff --git a/.coveragerc b/.coveragerc
index 3a3e2cd..0151e07 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,10 +1,6 @@
-[run]
-branch = True
-
 [report]
 omit =
     */samples/*
-    */conftest.py
     # Don't report coverage over platform-specific modules.
     oauth2client/contrib/_fcntl_opener.py
     oauth2client/contrib/_win32_opener.py
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 2ce3395..0000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,3 +0,0 @@
-**Note**: oauth2client is now deprecated. As such, it is unlikely that we will
-address or respond to your issue. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
deleted file mode 100644
index 1fbd4d2..0000000
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ /dev/null
@@ -1,3 +0,0 @@
-**Note**: oauth2client is now deprecated. As such, it is unlikely that we will
-review or merge to your pull request. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
diff --git a/.gitignore b/.gitignore
index 0bc898c..89c1121 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,7 +10,6 @@
 
 # Test files
 .tox/
-.cache/
 
 # Django test database
 db.sqlite3
diff --git a/.travis.yml b/.travis.yml
index 47570be..a8c01fa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,46 +1,41 @@
 language: python
+python: 2.7
 sudo: false
-
+# TODO(issue 532): Fix syntax when 3.5 is natively available upstream
 matrix:
   include:
-  - python: 2.7
-    env: TOX_ENV=flake8
-  - python: 2.7
-    env: TOX_ENV=docs
-  - python: 2.7
-    env: TOX_ENV=gae
-  - python: 2.7
-    env: TOX_ENV=py27
-  - python: 3.4
-    env: TOX_ENV=py34
-  - python: 3.5
-    env: TOX_ENV=py35
-  - python: 2.7
-    env: TOX_ENV=system-tests
-  - python: 3.4
-    env: TOX_ENV=system-tests3
-  - python: 2.7
-    env: TOX_ENV=cover
+    - python: 3.5
+      env:
+      - TOX_ENV=py35
 env:
+  matrix:
+  - TOX_ENV=py26
+  - TOX_ENV=py27
+  - TOX_ENV=py33
+  - TOX_ENV=py34
+  - TOX_ENV=pypy
+  - TOX_ENV=docs
+  - TOX_ENV=system-tests
+  - TOX_ENV=system-tests3
+  - TOX_ENV=gae
+  - TOX_ENV=flake8
   global:
   - GAE_PYTHONPATH=${HOME}/.cache/google_appengine
 cache:
   directories:
   - ${HOME}/.cache
-  - ${HOME}/.pyenv
 install:
 - ./scripts/install.sh
 script:
 - ./scripts/run.sh
 after_success:
-- if [[ "${TOX_ENV}" == "cover" ]]; then coveralls; fi
+- if [[ "${TOX_ENV}" == "gae" ]]; then tox -e coveralls; fi
 notifications:
   email: false
 
 deploy:
   provider: pypi
   user: gcloudpypi
-  distributions: sdist bdist_wheel
   password:
     secure: "C9ImNa5kbdnrQNfX9ww4PUtQIr3tN+nfxl7eDkP1B8Qr0QNYjrjov7x+DLImkKvmoJd3dxYtYIpLE9esObUHu0gKHYxqymNHtuAAyoBOUfPtmp0vIEse9brNKMtaey5Ngk7ZWz9EHKBBqRHxqgN+Giby+K9Ta3K3urJIq6urYhE="
   on:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2523d29..bf33dea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,84 +1,5 @@
 # CHANGELOG
 
-## v4.1.3
-
-**Note**: oauth2client is deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
-
-* Changed OAuth2 endpoints to use oauth2.googleapis.com variants. (#742)
-
-## v4.1.2
-
-**Note**: oauth2client is deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
-
-Bug fixes:
-* Fix packaging issue had erroneously installed the test package. (#688)
-
-## v4.1.1
-
-**Note**: oauth2client is deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
-
-New features:
-* Allow passing prompt='consent' via the flow_from_clientsecrets. (#717)
-
-## v4.1.0
-
-**Note**: oauth2client is now deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/).
-
-New features:
-* Allow customizing the GCE metadata service address via an env var. (#704)
-* Store original encoded and signed identity JWT in OAuth2Credentials. (#680)
-* Use jsonpickle in django contrib, if available. (#676)
-
-Bug fixes:
-* Typo fixes. (#668, #697)
-* Remove b64 padding from PKCE values, per RFC7636. (#683)
-* Include LICENSE in Manifest.in. (#694)
-* Fix tests and CI. (#705, #712, #713)
-* Escape callback error code in flask_util. (#710)
-
-## v4.0.0
-
-New features:
-* New Django samples. (#636)
-* Add support for RFC7636 PKCE. (#588)
-* Release as a universal wheel. (#665)
-
-Bug fixes:
-* Fix django authorization redirect by correctly checking validity of credentials. (#651)
-* Correct query loss when using parse_qsl to dict. (#622)
-* Switch django models from pickle to jsonpickle. (#614)
-* Support new MIDDLEWARE Django 1.10 setting. (#623)
-* Remove usage of os.environ.setdefault. (#621)
-* Handle missing storage files correctly. (#576)
-* Try to revoke token with POST when getting a 405. (#662)
-
-Internal changes:
-* Use transport module for GCE environment check. (#612)
-* Remove __author__ lines and add contributors.md. (#627)
-* Clean up imports. (#625)
-* Use transport.request in tests. (#607)
-* Drop unittest2 dependency (#610)
-* Remove backslash line continuations. (#608)
-* Use transport helpers in system tests. (#606)
-* Clean up usage of HTTP mocks in tests. (#605)
-* Remove all uses of MagicMock. (#598)
-* Migrate test runner to pytest. (#569)
-* Merge util.py and _helpers.py. (#579)
-* Remove httplib2 imports from non-transport modules. (#577)
-
-Breaking changes:
-* Drop Python 3.3 support. (#603)
-* Drop Python 2.6 support. (#590)
-* Remove multistore_file. (#589)
-
 ## v3.0.0
 
 * Populate `token_expiry` for GCE credentials. (#473)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index 46b2a08..0000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Contributor Code of Conduct
-
-As contributors and maintainers of this project,
-and in the interest of fostering an open and welcoming community,
-we pledge to respect all people who contribute through reporting issues,
-posting feature requests, updating documentation,
-submitting pull requests or patches, and other activities.
-
-We are committed to making participation in this project
-a harassment-free experience for everyone,
-regardless of level of experience, gender, gender identity and expression,
-sexual orientation, disability, personal appearance,
-body size, race, ethnicity, age, religion, or nationality.
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery
-* Personal attacks
-* Trolling or insulting/derogatory comments
-* Public or private harassment
-* Publishing other's private information,
-such as physical or electronic
-addresses, without explicit permission
-* Other unethical or unprofessional conduct.
-
-Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct.
-By adopting this Code of Conduct,
-project maintainers commit themselves to fairly and consistently
-applying these principles to every aspect of managing this project.
-Project maintainers who do not follow or enforce the Code of Conduct
-may be permanently removed from the project team.
-
-This code of conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior
-may be reported by opening an issue
-or contacting one or more of the project maintainers.
-
-This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
-available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 990c534..15b9455 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -127,6 +127,10 @@
     least version 2.6 of `pypy` installed. See the [docs][13] for
     more information.
 
+-   **Note** that `django` related tests are turned off for Python 2.6
+    and 3.3. This is because `django` dropped support for
+    [2.6 in `django==1.7`][14] and for [3.3 in `django==1.9`][15].
+
 Running System Tests
 --------------------
 
@@ -198,5 +202,7 @@
 [11]: #include-tests
 [12]: #make-the-pull-request
 [13]: https://oauth2client.readthedocs.io/en/latest/#using-pypy
+[14]: https://docs.djangoproject.com/en/1.7/faq/install/#what-python-version-can-i-use-with-django
+[15]: https://docs.djangoproject.com/en/1.9/faq/install/#what-python-version-can-i-use-with-django
 [GooglePythonStyle]: https://google.github.io/styleguide/pyguide.html
 [GitCommitRules]: http://chris.beams.io/posts/git-commit/#seven-rules
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
deleted file mode 100644
index 00bd09f..0000000
--- a/CONTRIBUTORS.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Contribors to oauth2client
-
-## Maintainers
-
-* [Nathaniel Manista](https://github.com/nathanielmanistaatgoogle)
-* [Jon Wayne Parrott](https://github.com/jonparrott)
-* [Danny Hermes](https://github.com/dhermes)
-
-Previous maintainers:
-
-* [Craig Citro](https://github.com/craigcitro)
-* [Joe Gregorio](https://github.com/jcgregorio)
-
-## Contributors
-
-This list is generated from git commit authors.
-
-* aalexand <aalexand@google.com>
-* Aaron <aaronwinter@users.noreply.github.com>
-* Adam Chainz <me@adamj.eu>
-* ade@google.com
-* Alexandre Vivien <alx.vivien@gmail.com>
-* Ali Afshar <afshar@google.com>
-* Andrzej Pragacz <apragacz@o2.pl>
-* api.nickm@gmail.com
-* Ben Demaree <bendemaree@gmail.com>
-* Bill Prin <waprin@gmail.com, waprin@google.com>
-* Brendan McCollam <brendan@mccoll.am, bmccollam@uchicago.edu>
-* Craig Citro <craigcitro@gmail.com, craigcitro@google.com>
-* Dan Ring <dfring@gmail.com>
-* Daniel Hermes <dhermes@google.com, daniel.j.hermes@gmail.com>
-* Danilo Akamine <danilowz@gmail.com>
-* daryl herzmann <akrherz@iastate.edu>
-* dlorenc <lorenc.d@gmail.com>
-* Dominik MiedziƄski <dominik@mdzn.pl>
-* dr. Kertész Csaba-Zoltán <cskertesz@gmail.com>
-* Dustin Farris <dustin@dustinfarris.com>
-* Eddie Warner <happyspace@gmail.com>
-* Edwin Amsler <EdwinGuy@GMail.com>
-* elibixby <elibixby@google.com>
-* Emanuele Pucciarelli <ep@acm.org>
-* Eric Koleda <eric.koleda@google.com>
-* Frederik Creemers <frederikcreemers@gmail.com>
-* Guido van Rossum <guido@google.com>
-* Harsh Vardhan <harshvd95@gmail.com>
-* Herr Kaste <thdz.x@gmx.net>
-* INADA Naoki <inada-n@klab.com>
-* JacobMoshenko <moshenko@google.com>
-* Jay Lee <jay0lee@gmail.com>
-* Jed Hartman <jhartman@google.com>
-* Jeff Terrace <jterrace@gmail.com, jterrace@google.com>
-* Jeffrey Sorensen <sorensenjs@users.noreply.github.com>
-* Jeremi Joslin <jeremi@collabspot.com>
-* Jin Liu <liujin@google.com>
-* Joe Beda <jbeda@google.com>
-* Joe Gregorio <jcgregorio@google.com, joe.gregorio@gmail.com>
-* Johan Euphrosine <proppy@google.com>
-* John Asmuth <jasmuth@gmail.com, jasmuth@google.com>
-* John Vandenberg <jayvdb@gmail.com>
-* Jon Wayne Parrott <jon.wayne.parrott@gmail.com, jonwayne@google.com>
-* Jose Alcerreca <jalc@google.com>
-* KCs <cskertesz@gmail.com>
-* Keith Maxwell <keith.maxwell@gmail.com>
-* Ken Payson <kpayson@google.com>
-* Kevin Regan <regank@google.com>
-* lraccomando <lraccomando@gmail.com>
-* Luar Roji <cyberplant@users.noreply.github.com>
-* Luke Blanshard <leadpipe@google.com>
-* Marc Cohen <marccohen@google.com>
-* Mark Pellegrini <markpell@google.com>
-* Martin Trigaux <mat@odoo.com>
-* Matt McDonald <mmcdonald@google.com>
-* Nathan Naze <nanaze@gmail.com>
-* Nathaniel Manista <nathaniel@google.com>
-* Orest Bolohan <orest@google.com>
-* Pat Ferate <pferate@gmail.com>
-* Patrick Costello <pcostello@google.com>
-* Rafe Kaplan <rafek@google.com>
-* rahulpaul@google.com <rahulpaul@google.com>
-* RM Saksida <rsaksida@gmail.com>
-* Robert Kaplow <rkaplow@google.com>
-* Robert Spies <wilford@google.com>
-* Sergei Trofimovich <siarheit@google.com>
-* sgomes@google.com <sgomes@google.com>
-* Simon Cadman <src@niftiestsoftware.com>
-* soltanmm <soltanmm@users.noreply.github.com>
-* Sébastien de Melo <sebastien.de-melo@ubicast.eu>
-* takuya sato <sato-taku@klab.com>
-* thobrla <thobrla@google.com>
-* Tom Miller <tom.h.miller@gmail.com>
-* Tony Aiuto <aiuto@google.com>
-* Travis Hobrla <thobrla@google.com>
-* Veres Lajos <vlajos@gmail.com>
-* Vivek Seth <vivekseth.m@gmail.com>
-* Éamonn McManus <eamonn@mcmanus.net>
diff --git a/LICENSE b/LICENSE
index c8d76df..b506d50 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,205 +1,16 @@
+ Copyright 2014 Google Inc.
 
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
+ 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
 
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+      http://www.apache.org/licenses/LICENSE-2.0
 
-   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.
+ 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.
 
 Dependent Modules
 =================
@@ -207,4 +18,5 @@
 This code has the following dependencies
 above and beyond the Python standard library:
 
+uritemplates - Apache License 2.0
 httplib2 - MIT License
diff --git a/MANIFEST.in b/MANIFEST.in
index 4f2ba45..39f5637 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
-include README.md LICENSE CHANGELOG.md
-recursive-include tests *
+include README.md
+recursive-exclude tests *
diff --git a/METADATA b/METADATA
index 97c5852..6a258aa 100644
--- a/METADATA
+++ b/METADATA
@@ -1,5 +1,7 @@
 name: "oauth2client"
-description: "This is a client library for accessing resources protected by OAuth 2.0."
+description:
+    "This is a client library for accessing resources protected by OAuth 2.0."
+
 third_party {
   url {
     type: HOMEPAGE
@@ -9,10 +11,6 @@
     type: GIT
     value: "https://github.com/google/oauth2client"
   }
-  version: "v4.1.3"
-  last_upgrade_date {
-    year: 2019
-    month: 2
-    day: 1
-  }
+  version: "v3.0.0"
+  last_upgrade_date { year: 2018 month: 6 day: 6 }
 }
diff --git a/README.md b/README.md
index 5e7aade..17e69fc 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,6 @@
 
 This is a client library for accessing resources protected by OAuth 2.0.
 
-**Note**: oauth2client is now deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-[google-auth](https://google-auth.readthedocs.io) and [oauthlib](http://oauthlib.readthedocs.io/). For more details on the deprecation, see [oauth2client deprecation](https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html).
-
 Installation
 ============
 
@@ -27,7 +23,7 @@
 Supported Python Versions
 =========================
 
-We support Python 2.7 and 3.4+. More information [in the docs][2].
+We support Python 2.6, 2.7, 3.3+. More information [in the docs][2].
 
 [1]: https://github.com/google/oauth2client/blob/master/CONTRIBUTING.md
 [2]: https://oauth2client.readthedocs.io/#supported-python-versions
diff --git a/docs/index.rst b/docs/index.rst
index 4b9f38a..0543e1a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,14 +1,6 @@
 oauth2client
 ============
 
-.. note:: oauth2client is now deprecated. No more features will be added to the
-libraries and the core team is turning down support. We recommend you use
-`google-auth`_ and `oauthlib`_. For more details on the deprecation, see `oauth2client deprecation`_.
-
-.. _google-auth: https://google-auth.readthedocs.io
-.. _oauthlib: http://oauthlib.readthedocs.io/
-.. _oauth2client deprecation: https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html
-
 *making OAuth2 just a little less painful*
 
 ``oauth2client`` makes it easy to interact with OAuth2-protected resources,
@@ -115,24 +107,18 @@
 Supported Python Versions
 -------------------------
 
-We support Python 2.7 and 3.4+. (Whatever this file says, the truth is
+We support Python 2.6, 2.7, 3.3+. (Whatever this file says, the truth is
 always represented by our `tox.ini`_).
 
 .. _tox.ini: https://github.com/google/oauth2client/blob/master/tox.ini
 
 We explicitly decided to support Python 3 beginning with version
-3.4. Reasons for this include:
+3.3. Reasons for this include:
 
 * Encouraging use of newest versions of Python 3
 * Following the lead of prominent `open-source projects`_
 * Unicode literal support which
   allows for a cleaner codebase that works in both Python 2 and Python 3
-* Prominent projects like `django`_ have `dropped support`_ for earlier
-  versions (3.3 support dropped in December 2015, and 2.6 support
-  `dropped`_ in September 2014)
 
 .. _open-source projects: http://docs.python-requests.org/en/latest/
 .. _Unicode literal support: https://www.python.org/dev/peps/pep-0414/
-.. _django: https://docs.djangoproject.com/
-.. _dropped support: https://docs.djangoproject.com/en/1.9/faq/install/#what-python-version-can-i-use-with-django
-.. _dropped: https://docs.djangoproject.com/en/1.7/faq/install/#what-python-version-can-i-use-with-django
diff --git a/docs/source/oauth2client.client.rst b/docs/source/oauth2client.client.rst
index edb2f97..f3b1832 100644
--- a/docs/source/oauth2client.client.rst
+++ b/docs/source/oauth2client.client.rst
@@ -1,5 +1,5 @@
-oauth2client\.client module
-===========================
+oauth2client.client module
+==========================
 
 .. automodule:: oauth2client.client
     :members:
diff --git a/docs/source/oauth2client.clientsecrets.rst b/docs/source/oauth2client.clientsecrets.rst
index a839444..d666564 100644
--- a/docs/source/oauth2client.clientsecrets.rst
+++ b/docs/source/oauth2client.clientsecrets.rst
@@ -1,5 +1,5 @@
-oauth2client\.clientsecrets module
-==================================
+oauth2client.clientsecrets module
+=================================
 
 .. automodule:: oauth2client.clientsecrets
     :members:
diff --git a/docs/source/oauth2client.contrib.appengine.rst b/docs/source/oauth2client.contrib.appengine.rst
index 5051495..7f3d5e2 100644
--- a/docs/source/oauth2client.contrib.appengine.rst
+++ b/docs/source/oauth2client.contrib.appengine.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.appengine module
-=======================================
+oauth2client.contrib.appengine module
+=====================================
 
 .. automodule:: oauth2client.contrib.appengine
     :members:
diff --git a/docs/source/oauth2client.contrib.devshell.rst b/docs/source/oauth2client.contrib.devshell.rst
index 66691bd..20d5c41 100644
--- a/docs/source/oauth2client.contrib.devshell.rst
+++ b/docs/source/oauth2client.contrib.devshell.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.devshell module
-======================================
+oauth2client.contrib.devshell module
+====================================
 
 .. automodule:: oauth2client.contrib.devshell
     :members:
diff --git a/docs/source/oauth2client.contrib.dictionary_storage.rst b/docs/source/oauth2client.contrib.dictionary_storage.rst
index b94a079..1b59a2c 100644
--- a/docs/source/oauth2client.contrib.dictionary_storage.rst
+++ b/docs/source/oauth2client.contrib.dictionary_storage.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.dictionary\_storage module
-=================================================
+oauth2client.contrib.dictionary_storage module
+==============================================
 
 .. automodule:: oauth2client.contrib.dictionary_storage
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.apps.rst b/docs/source/oauth2client.contrib.django_util.apps.rst
index 1ffe1af..b7c91ae 100644
--- a/docs/source/oauth2client.contrib.django_util.apps.rst
+++ b/docs/source/oauth2client.contrib.django_util.apps.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.apps module
-================================================
+oauth2client.contrib.django_util.apps module
+============================================
 
 .. automodule:: oauth2client.contrib.django_util.apps
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.decorators.rst b/docs/source/oauth2client.contrib.django_util.decorators.rst
index 2eb9dcf..07350bc 100644
--- a/docs/source/oauth2client.contrib.django_util.decorators.rst
+++ b/docs/source/oauth2client.contrib.django_util.decorators.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.decorators module
-======================================================
+oauth2client.contrib.django_util.decorators module
+==================================================
 
 .. automodule:: oauth2client.contrib.django_util.decorators
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.models.rst b/docs/source/oauth2client.contrib.django_util.models.rst
index 91d3b8d..4be59d3 100644
--- a/docs/source/oauth2client.contrib.django_util.models.rst
+++ b/docs/source/oauth2client.contrib.django_util.models.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.models module
-==================================================
+oauth2client.contrib.django_util.models module
+==============================================
 
 .. automodule:: oauth2client.contrib.django_util.models
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.rst b/docs/source/oauth2client.contrib.django_util.rst
index 8247134..f60195a 100644
--- a/docs/source/oauth2client.contrib.django_util.rst
+++ b/docs/source/oauth2client.contrib.django_util.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util package
-===========================================
+oauth2client.contrib.django_util package
+========================================
 
 Submodules
 ----------
diff --git a/docs/source/oauth2client.contrib.django_util.signals.rst b/docs/source/oauth2client.contrib.django_util.signals.rst
index 9a18252..70b5d2d 100644
--- a/docs/source/oauth2client.contrib.django_util.signals.rst
+++ b/docs/source/oauth2client.contrib.django_util.signals.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.signals module
-===================================================
+oauth2client.contrib.django_util.signals module
+===============================================
 
 .. automodule:: oauth2client.contrib.django_util.signals
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.site.rst b/docs/source/oauth2client.contrib.django_util.site.rst
index 5f5dae0..a271b98 100644
--- a/docs/source/oauth2client.contrib.django_util.site.rst
+++ b/docs/source/oauth2client.contrib.django_util.site.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.site module
-================================================
+oauth2client.contrib.django_util.site module
+============================================
 
 .. automodule:: oauth2client.contrib.django_util.site
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.storage.rst b/docs/source/oauth2client.contrib.django_util.storage.rst
index 4340a4c..393e738 100644
--- a/docs/source/oauth2client.contrib.django_util.storage.rst
+++ b/docs/source/oauth2client.contrib.django_util.storage.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.storage module
-===================================================
+oauth2client.contrib.django_util.storage module
+===============================================
 
 .. automodule:: oauth2client.contrib.django_util.storage
     :members:
diff --git a/docs/source/oauth2client.contrib.django_util.views.rst b/docs/source/oauth2client.contrib.django_util.views.rst
index dfba37f..4cbbea0 100644
--- a/docs/source/oauth2client.contrib.django_util.views.rst
+++ b/docs/source/oauth2client.contrib.django_util.views.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.django\_util\.views module
-=================================================
+oauth2client.contrib.django_util.views module
+=============================================
 
 .. automodule:: oauth2client.contrib.django_util.views
     :members:
diff --git a/docs/source/oauth2client.contrib.flask_util.rst b/docs/source/oauth2client.contrib.flask_util.rst
index c11c9ba..8ff2355 100644
--- a/docs/source/oauth2client.contrib.flask_util.rst
+++ b/docs/source/oauth2client.contrib.flask_util.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.flask\_util module
-=========================================
+oauth2client.contrib.flask_util module
+======================================
 
 .. automodule:: oauth2client.contrib.flask_util
     :members:
diff --git a/docs/source/oauth2client.contrib.gce.rst b/docs/source/oauth2client.contrib.gce.rst
index d0b7a15..a3748b6 100644
--- a/docs/source/oauth2client.contrib.gce.rst
+++ b/docs/source/oauth2client.contrib.gce.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.gce module
-=================================
+oauth2client.contrib.gce module
+===============================
 
 .. automodule:: oauth2client.contrib.gce
     :members:
diff --git a/docs/source/oauth2client.contrib.keyring_storage.rst b/docs/source/oauth2client.contrib.keyring_storage.rst
index 286e84a..0fd7476 100644
--- a/docs/source/oauth2client.contrib.keyring_storage.rst
+++ b/docs/source/oauth2client.contrib.keyring_storage.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.keyring\_storage module
-==============================================
+oauth2client.contrib.keyring_storage module
+===========================================
 
 .. automodule:: oauth2client.contrib.keyring_storage
     :members:
diff --git a/docs/source/oauth2client.contrib.locked_file.rst b/docs/source/oauth2client.contrib.locked_file.rst
new file mode 100644
index 0000000..1076e29
--- /dev/null
+++ b/docs/source/oauth2client.contrib.locked_file.rst
@@ -0,0 +1,7 @@
+oauth2client.contrib.locked_file module
+=======================================
+
+.. automodule:: oauth2client.contrib.locked_file
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/docs/source/oauth2client.contrib.multiprocess_file_storage.rst b/docs/source/oauth2client.contrib.multiprocess_file_storage.rst
index eb6c0c0..6f683a0 100644
--- a/docs/source/oauth2client.contrib.multiprocess_file_storage.rst
+++ b/docs/source/oauth2client.contrib.multiprocess_file_storage.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.multiprocess\_file\_storage module
-=========================================================
+oauth2client.contrib.multiprocess_file_storage module
+=====================================================
 
 .. automodule:: oauth2client.contrib.multiprocess_file_storage
     :members:
diff --git a/docs/source/oauth2client.contrib.multistore_file.rst b/docs/source/oauth2client.contrib.multistore_file.rst
new file mode 100644
index 0000000..2787b10
--- /dev/null
+++ b/docs/source/oauth2client.contrib.multistore_file.rst
@@ -0,0 +1,7 @@
+oauth2client.contrib.multistore_file module
+===========================================
+
+.. automodule:: oauth2client.contrib.multistore_file
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/docs/source/oauth2client.contrib.rst b/docs/source/oauth2client.contrib.rst
index 644278d..44be6f9 100644
--- a/docs/source/oauth2client.contrib.rst
+++ b/docs/source/oauth2client.contrib.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib package
-=============================
+oauth2client.contrib package
+============================
 
 Subpackages
 -----------
@@ -19,7 +19,9 @@
    oauth2client.contrib.flask_util
    oauth2client.contrib.gce
    oauth2client.contrib.keyring_storage
+   oauth2client.contrib.locked_file
    oauth2client.contrib.multiprocess_file_storage
+   oauth2client.contrib.multistore_file
    oauth2client.contrib.sqlalchemy
    oauth2client.contrib.xsrfutil
 
diff --git a/docs/source/oauth2client.contrib.sqlalchemy.rst b/docs/source/oauth2client.contrib.sqlalchemy.rst
index c4a634e..94eeeec 100644
--- a/docs/source/oauth2client.contrib.sqlalchemy.rst
+++ b/docs/source/oauth2client.contrib.sqlalchemy.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.sqlalchemy module
-========================================
+oauth2client.contrib.sqlalchemy module
+======================================
 
 .. automodule:: oauth2client.contrib.sqlalchemy
     :members:
diff --git a/docs/source/oauth2client.contrib.xsrfutil.rst b/docs/source/oauth2client.contrib.xsrfutil.rst
index eec497d..dd5e8d6 100644
--- a/docs/source/oauth2client.contrib.xsrfutil.rst
+++ b/docs/source/oauth2client.contrib.xsrfutil.rst
@@ -1,5 +1,5 @@
-oauth2client\.contrib\.xsrfutil module
-======================================
+oauth2client.contrib.xsrfutil module
+====================================
 
 .. automodule:: oauth2client.contrib.xsrfutil
     :members:
diff --git a/docs/source/oauth2client.crypt.rst b/docs/source/oauth2client.crypt.rst
index d03cf50..c3b6acc 100644
--- a/docs/source/oauth2client.crypt.rst
+++ b/docs/source/oauth2client.crypt.rst
@@ -1,5 +1,5 @@
-oauth2client\.crypt module
-==========================
+oauth2client.crypt module
+=========================
 
 .. automodule:: oauth2client.crypt
     :members:
diff --git a/docs/source/oauth2client.file.rst b/docs/source/oauth2client.file.rst
index bf804ff..52a9e94 100644
--- a/docs/source/oauth2client.file.rst
+++ b/docs/source/oauth2client.file.rst
@@ -1,5 +1,5 @@
-oauth2client\.file module
-=========================
+oauth2client.file module
+========================
 
 .. automodule:: oauth2client.file
     :members:
diff --git a/docs/source/oauth2client.rst b/docs/source/oauth2client.rst
index 11f64e4..65de8ac 100644
--- a/docs/source/oauth2client.rst
+++ b/docs/source/oauth2client.rst
@@ -20,6 +20,7 @@
    oauth2client.service_account
    oauth2client.tools
    oauth2client.transport
+   oauth2client.util
 
 Module contents
 ---------------
diff --git a/docs/source/oauth2client.service_account.rst b/docs/source/oauth2client.service_account.rst
index c370246..0d3b382 100644
--- a/docs/source/oauth2client.service_account.rst
+++ b/docs/source/oauth2client.service_account.rst
@@ -1,5 +1,5 @@
-oauth2client\.service\_account module
-=====================================
+oauth2client.service_account module
+===================================
 
 .. automodule:: oauth2client.service_account
     :members:
diff --git a/docs/source/oauth2client.tools.rst b/docs/source/oauth2client.tools.rst
index 86be326..240ad52 100644
--- a/docs/source/oauth2client.tools.rst
+++ b/docs/source/oauth2client.tools.rst
@@ -1,5 +1,5 @@
-oauth2client\.tools module
-==========================
+oauth2client.tools module
+=========================
 
 .. automodule:: oauth2client.tools
     :members:
diff --git a/docs/source/oauth2client.transport.rst b/docs/source/oauth2client.transport.rst
index c5440f1..1c6dbb0 100644
--- a/docs/source/oauth2client.transport.rst
+++ b/docs/source/oauth2client.transport.rst
@@ -1,5 +1,5 @@
-oauth2client\.transport module
-==============================
+oauth2client.transport module
+=============================
 
 .. automodule:: oauth2client.transport
     :members:
diff --git a/docs/source/oauth2client.util.rst b/docs/source/oauth2client.util.rst
new file mode 100644
index 0000000..21dc8c8
--- /dev/null
+++ b/docs/source/oauth2client.util.rst
@@ -0,0 +1,7 @@
+oauth2client.util module
+========================
+
+.. automodule:: oauth2client.util
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/oauth2client/__init__.py b/oauth2client/__init__.py
index 92bc191..28384bb 100644
--- a/oauth2client/__init__.py
+++ b/oauth2client/__init__.py
@@ -14,11 +14,10 @@
 
 """Client library for using OAuth2, especially with Google APIs."""
 
-__version__ = '4.1.3'
+__version__ = '3.0.0'
 
 GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
-GOOGLE_DEVICE_URI = 'https://oauth2.googleapis.com/device/code'
-GOOGLE_REVOKE_URI = 'https://oauth2.googleapis.com/revoke'
-GOOGLE_TOKEN_URI = 'https://oauth2.googleapis.com/token'
-GOOGLE_TOKEN_INFO_URI = 'https://oauth2.googleapis.com/tokeninfo'
-
+GOOGLE_DEVICE_URI = 'https://accounts.google.com/o/oauth2/device/code'
+GOOGLE_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke'
+GOOGLE_TOKEN_URI = 'https://www.googleapis.com/oauth2/v4/token'
+GOOGLE_TOKEN_INFO_URI = 'https://www.googleapis.com/oauth2/v3/tokeninfo'
diff --git a/oauth2client/_helpers.py b/oauth2client/_helpers.py
index e912397..cb959c5 100644
--- a/oauth2client/_helpers.py
+++ b/oauth2client/_helpers.py
@@ -11,248 +11,12 @@
 # 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.
-
 """Helper functions for commonly used utilities."""
 
 import base64
-import functools
-import inspect
 import json
-import logging
-import os
-import warnings
 
 import six
-from six.moves import urllib
-
-
-logger = logging.getLogger(__name__)
-
-POSITIONAL_WARNING = 'WARNING'
-POSITIONAL_EXCEPTION = 'EXCEPTION'
-POSITIONAL_IGNORE = 'IGNORE'
-POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
-                            POSITIONAL_IGNORE])
-
-positional_parameters_enforcement = POSITIONAL_WARNING
-
-_SYM_LINK_MESSAGE = 'File: {0}: Is a symbolic link.'
-_IS_DIR_MESSAGE = '{0}: Is a directory'
-_MISSING_FILE_MESSAGE = 'Cannot access {0}: No such file or directory'
-
-
-def positional(max_positional_args):
-    """A decorator to declare that only the first N arguments my be positional.
-
-    This decorator makes it easy to support Python 3 style keyword-only
-    parameters. For example, in Python 3 it is possible to write::
-
-        def fn(pos1, *, kwonly1=None, kwonly1=None):
-            ...
-
-    All named parameters after ``*`` must be a keyword::
-
-        fn(10, 'kw1', 'kw2')  # Raises exception.
-        fn(10, kwonly1='kw1')  # Ok.
-
-    Example
-    ^^^^^^^
-
-    To define a function like above, do::
-
-        @positional(1)
-        def fn(pos1, kwonly1=None, kwonly2=None):
-            ...
-
-    If no default value is provided to a keyword argument, it becomes a
-    required keyword argument::
-
-        @positional(0)
-        def fn(required_kw):
-            ...
-
-    This must be called with the keyword parameter::
-
-        fn()  # Raises exception.
-        fn(10)  # Raises exception.
-        fn(required_kw=10)  # Ok.
-
-    When defining instance or class methods always remember to account for
-    ``self`` and ``cls``::
-
-        class MyClass(object):
-
-            @positional(2)
-            def my_method(self, pos1, kwonly1=None):
-                ...
-
-            @classmethod
-            @positional(2)
-            def my_method(cls, pos1, kwonly1=None):
-                ...
-
-    The positional decorator behavior is controlled by
-    ``_helpers.positional_parameters_enforcement``, which may be set to
-    ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
-    ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
-    nothing, respectively, if a declaration is violated.
-
-    Args:
-        max_positional_arguments: Maximum number of positional arguments. All
-                                  parameters after the this index must be
-                                  keyword only.
-
-    Returns:
-        A decorator that prevents using arguments after max_positional_args
-        from being used as positional parameters.
-
-    Raises:
-        TypeError: if a key-word only argument is provided as a positional
-                   parameter, but only if
-                   _helpers.positional_parameters_enforcement is set to
-                   POSITIONAL_EXCEPTION.
-    """
-
-    def positional_decorator(wrapped):
-        @functools.wraps(wrapped)
-        def positional_wrapper(*args, **kwargs):
-            if len(args) > max_positional_args:
-                plural_s = ''
-                if max_positional_args != 1:
-                    plural_s = 's'
-                message = ('{function}() takes at most {args_max} positional '
-                           'argument{plural} ({args_given} given)'.format(
-                               function=wrapped.__name__,
-                               args_max=max_positional_args,
-                               args_given=len(args),
-                               plural=plural_s))
-                if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
-                    raise TypeError(message)
-                elif positional_parameters_enforcement == POSITIONAL_WARNING:
-                    logger.warning(message)
-            return wrapped(*args, **kwargs)
-        return positional_wrapper
-
-    if isinstance(max_positional_args, six.integer_types):
-        return positional_decorator
-    else:
-        args, _, _, defaults = inspect.getargspec(max_positional_args)
-        return positional(len(args) - len(defaults))(max_positional_args)
-
-
-def scopes_to_string(scopes):
-    """Converts scope value to a string.
-
-    If scopes is a string then it is simply passed through. If scopes is an
-    iterable then a string is returned that is all the individual scopes
-    concatenated with spaces.
-
-    Args:
-        scopes: string or iterable of strings, the scopes.
-
-    Returns:
-        The scopes formatted as a single string.
-    """
-    if isinstance(scopes, six.string_types):
-        return scopes
-    else:
-        return ' '.join(scopes)
-
-
-def string_to_scopes(scopes):
-    """Converts stringifed scope value to a list.
-
-    If scopes is a list then it is simply passed through. If scopes is an
-    string then a list of each individual scope is returned.
-
-    Args:
-        scopes: a string or iterable of strings, the scopes.
-
-    Returns:
-        The scopes in a list.
-    """
-    if not scopes:
-        return []
-    elif isinstance(scopes, six.string_types):
-        return scopes.split(' ')
-    else:
-        return scopes
-
-
-def parse_unique_urlencoded(content):
-    """Parses unique key-value parameters from urlencoded content.
-
-    Args:
-        content: string, URL-encoded key-value pairs.
-
-    Returns:
-        dict, The key-value pairs from ``content``.
-
-    Raises:
-        ValueError: if one of the keys is repeated.
-    """
-    urlencoded_params = urllib.parse.parse_qs(content)
-    params = {}
-    for key, value in six.iteritems(urlencoded_params):
-        if len(value) != 1:
-            msg = ('URL-encoded content contains a repeated value:'
-                   '%s -> %s' % (key, ', '.join(value)))
-            raise ValueError(msg)
-        params[key] = value[0]
-    return params
-
-
-def update_query_params(uri, params):
-    """Updates a URI with new query parameters.
-
-    If a given key from ``params`` is repeated in the ``uri``, then
-    the URI will be considered invalid and an error will occur.
-
-    If the URI is valid, then each value from ``params`` will
-    replace the corresponding value in the query parameters (if
-    it exists).
-
-    Args:
-        uri: string, A valid URI, with potential existing query parameters.
-        params: dict, A dictionary of query parameters.
-
-    Returns:
-        The same URI but with the new query parameters added.
-    """
-    parts = urllib.parse.urlparse(uri)
-    query_params = parse_unique_urlencoded(parts.query)
-    query_params.update(params)
-    new_query = urllib.parse.urlencode(query_params)
-    new_parts = parts._replace(query=new_query)
-    return urllib.parse.urlunparse(new_parts)
-
-
-def _add_query_parameter(url, name, value):
-    """Adds a query parameter to a url.
-
-    Replaces the current value if it already exists in the URL.
-
-    Args:
-        url: string, url to add the query parameter to.
-        name: string, query parameter name.
-        value: string, query parameter value.
-
-    Returns:
-        Updated query parameter. Does not update the url if value is None.
-    """
-    if value is None:
-        return url
-    else:
-        return update_query_params(url, {name: value})
-
-
-def validate_file(filename):
-    if os.path.islink(filename):
-        raise IOError(_SYM_LINK_MESSAGE.format(filename))
-    elif os.path.isdir(filename):
-        raise IOError(_IS_DIR_MESSAGE.format(filename))
-    elif not os.path.isfile(filename):
-        warnings.warn(_MISSING_FILE_MESSAGE.format(filename))
 
 
 def _parse_pem_key(raw_key_input):
diff --git a/oauth2client/_pkce.py b/oauth2client/_pkce.py
deleted file mode 100644
index e4952d8..0000000
--- a/oauth2client/_pkce.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2016 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.
-
-"""
-Utility functions for implementing Proof Key for Code Exchange (PKCE) by OAuth
-Public Clients
-
-See RFC7636.
-"""
-
-import base64
-import hashlib
-import os
-
-
-def code_verifier(n_bytes=64):
-    """
-    Generates a 'code_verifier' as described in section 4.1 of RFC 7636.
-
-    This is a 'high-entropy cryptographic random string' that will be
-    impractical for an attacker to guess.
-
-    Args:
-        n_bytes: integer between 31 and 96, inclusive. default: 64
-            number of bytes of entropy to include in verifier.
-
-    Returns:
-        Bytestring, representing urlsafe base64-encoded random data.
-    """
-    verifier = base64.urlsafe_b64encode(os.urandom(n_bytes)).rstrip(b'=')
-    # https://tools.ietf.org/html/rfc7636#section-4.1
-    # minimum length of 43 characters and a maximum length of 128 characters.
-    if len(verifier) < 43:
-        raise ValueError("Verifier too short. n_bytes must be > 30.")
-    elif len(verifier) > 128:
-        raise ValueError("Verifier too long. n_bytes must be < 97.")
-    else:
-        return verifier
-
-
-def code_challenge(verifier):
-    """
-    Creates a 'code_challenge' as described in section 4.2 of RFC 7636
-    by taking the sha256 hash of the verifier and then urlsafe
-    base64-encoding it.
-
-    Args:
-        verifier: bytestring, representing a code_verifier as generated by
-            code_verifier().
-
-    Returns:
-        Bytestring, representing a urlsafe base64-encoded sha256 hash digest,
-            without '=' padding.
-    """
-    digest = hashlib.sha256(verifier).digest()
-    return base64.urlsafe_b64encode(digest).rstrip(b'=')
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 7618960..8956443 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -34,11 +34,13 @@
 
 import oauth2client
 from oauth2client import _helpers
-from oauth2client import _pkce
 from oauth2client import clientsecrets
 from oauth2client import transport
+from oauth2client import util
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
 HAS_OPENSSL = False
 HAS_CRYPTO = False
 try:
@@ -98,20 +100,20 @@
 DEFAULT_ENV_NAME = 'UNKNOWN'
 
 # If set to True _get_environment avoid GCE check (_detect_gce_environment)
-NO_GCE_CHECK = os.getenv('NO_GCE_CHECK', 'False')
+NO_GCE_CHECK = os.environ.setdefault('NO_GCE_CHECK', 'False')
 
 # Timeout in seconds to wait for the GCE metadata server when detecting the
 # GCE environment.
 try:
-    GCE_METADATA_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3))
+    GCE_METADATA_TIMEOUT = int(
+        os.environ.setdefault('GCE_METADATA_TIMEOUT', '3'))
 except ValueError:  # pragma: NO COVER
     GCE_METADATA_TIMEOUT = 3
 
 _SERVER_SOFTWARE = 'SERVER_SOFTWARE'
-_GCE_METADATA_URI = 'http://' + os.getenv('GCE_METADATA_IP', '169.254.169.254')
-_METADATA_FLAVOR_HEADER = 'metadata-flavor'  # lowercase header
+_GCE_METADATA_HOST = '169.254.169.254'
+_METADATA_FLAVOR_HEADER = 'Metadata-Flavor'
 _DESIRED_METADATA_FLAVOR = 'Google'
-_GCE_HEADERS = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
 
 # Expose utcnow() at module level to allow for
 # easier testing (by replacing with a stub).
@@ -438,6 +440,23 @@
             self.release_lock()
 
 
+def _update_query_params(uri, params):
+    """Updates a URI with new query parameters.
+
+    Args:
+        uri: string, A valid URI, with potential existing query parameters.
+        params: dict, A dictionary of query parameters.
+
+    Returns:
+        The same URI but with the new query parameters added.
+    """
+    parts = urllib.parse.urlparse(uri)
+    query_params = dict(urllib.parse.parse_qsl(parts.query))
+    query_params.update(params)
+    new_parts = parts._replace(query=urllib.parse.urlencode(query_params))
+    return urllib.parse.urlunparse(new_parts)
+
+
 class OAuth2Credentials(Credentials):
     """Credentials object for OAuth 2.0.
 
@@ -447,11 +466,11 @@
     OAuth2Credentials objects may be safely pickled and unpickled.
     """
 
-    @_helpers.positional(8)
+    @util.positional(8)
     def __init__(self, access_token, client_id, client_secret, refresh_token,
                  token_expiry, token_uri, user_agent, revoke_uri=None,
                  id_token=None, token_response=None, scopes=None,
-                 token_info_uri=None, id_token_jwt=None):
+                 token_info_uri=None):
         """Create an instance of OAuth2Credentials.
 
         This constructor is not usually called by the user, instead
@@ -474,11 +493,8 @@
                             because some providers (e.g. wordpress.com) include
                             extra fields that clients may want.
             scopes: list, authorized scopes for these credentials.
-            token_info_uri: string, the URI for the token info endpoint.
-                            Defaults to None; scopes can not be refreshed if
-                            this is None.
-            id_token_jwt: string, the encoded and signed identity JWT. The
-                          decoded version of this is stored in id_token.
+          token_info_uri: string, the URI for the token info endpoint. Defaults
+                          to None; scopes can not be refreshed if this is None.
 
         Notes:
             store: callable, A callable that when passed a Credential
@@ -496,9 +512,8 @@
         self.user_agent = user_agent
         self.revoke_uri = revoke_uri
         self.id_token = id_token
-        self.id_token_jwt = id_token_jwt
         self.token_response = token_response
-        self.scopes = set(_helpers.string_to_scopes(scopes or []))
+        self.scopes = set(util.string_to_scopes(scopes or []))
         self.token_info_uri = token_info_uri
 
         # True if the credentials have been revoked or expired and can't be
@@ -542,7 +557,7 @@
             http: httplib2.Http, an http object to be used to make the refresh
                   request.
         """
-        self._refresh(http)
+        self._refresh(http.request)
 
     def revoke(self, http):
         """Revokes a refresh_token and makes the credentials void.
@@ -551,7 +566,7 @@
             http: httplib2.Http, an http object to be used to make the revoke
                   request.
         """
-        self._revoke(http)
+        self._revoke(http.request)
 
     def apply(self, headers):
         """Add the authorization to the headers.
@@ -577,7 +592,7 @@
             not have scopes. In both cases, you can use refresh_scopes() to
             obtain the canonical set of scopes.
         """
-        scopes = _helpers.string_to_scopes(scopes)
+        scopes = util.string_to_scopes(scopes)
         return set(scopes).issubset(self.scopes)
 
     def retrieve_scopes(self, http):
@@ -592,7 +607,7 @@
         Returns:
             A set of strings containing the canonical list of scopes.
         """
-        self._retrieve_scopes(http)
+        self._retrieve_scopes(http.request)
         return self.scopes
 
     @classmethod
@@ -625,7 +640,6 @@
             data['user_agent'],
             revoke_uri=data.get('revoke_uri', None),
             id_token=data.get('id_token', None),
-            id_token_jwt=data.get('id_token_jwt', None),
             token_response=data.get('token_response', None),
             scopes=data.get('scopes', None),
             token_info_uri=data.get('token_info_uri', None))
@@ -732,7 +746,7 @@
 
         return headers
 
-    def _refresh(self, http):
+    def _refresh(self, http_request):
         """Refreshes the access_token.
 
         This method first checks by reading the Storage object if available.
@@ -740,13 +754,15 @@
         refresh is completed.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          refresh request.
 
         Raises:
             HttpAccessTokenRefreshError: When the refresh fails.
         """
         if not self.store:
-            self._do_refresh_request(http)
+            self._do_refresh_request(http_request)
         else:
             self.store.acquire_lock()
             try:
@@ -758,15 +774,17 @@
                     logger.info('Updated access_token read from Storage')
                     self._updateFromCredential(new_cred)
                 else:
-                    self._do_refresh_request(http)
+                    self._do_refresh_request(http_request)
             finally:
                 self.store.release_lock()
 
-    def _do_refresh_request(self, http):
+    def _do_refresh_request(self, http_request):
         """Refresh the access_token using the refresh_token.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          refresh request.
 
         Raises:
             HttpAccessTokenRefreshError: When the refresh fails.
@@ -775,9 +793,8 @@
         headers = self._generate_refresh_request_headers()
 
         logger.info('Refreshing access_token')
-        resp, content = transport.request(
-            http, self.token_uri, method='POST',
-            body=body, headers=headers)
+        resp, content = http_request(
+            self.token_uri, method='POST', body=body, headers=headers)
         content = _helpers._from_bytes(content)
         if resp.status == http_client.OK:
             d = json.loads(content)
@@ -791,10 +808,8 @@
                 self.token_expiry = None
             if 'id_token' in d:
                 self.id_token = _extract_id_token(d['id_token'])
-                self.id_token_jwt = d['id_token']
             else:
                 self.id_token = None
-                self.id_token_jwt = None
             # On temporary refresh errors, the user does not actually have to
             # re-authorize, so we unflag here.
             self.invalid = False
@@ -804,7 +819,7 @@
             # An {'error':...} response body means the token is expired or
             # revoked, so we flag the credentials as such.
             logger.info('Failed to retrieve access token: %s', content)
-            error_msg = 'Invalid response {0}.'.format(resp.status)
+            error_msg = 'Invalid response {0}.'.format(resp['status'])
             try:
                 d = json.loads(content)
                 if 'error' in d:
@@ -818,19 +833,23 @@
                 pass
             raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
 
-    def _revoke(self, http):
+    def _revoke(self, http_request):
         """Revokes this credential and deletes the stored copy (if it exists).
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          revoke request.
         """
-        self._do_revoke(http, self.refresh_token or self.access_token)
+        self._do_revoke(http_request, self.refresh_token or self.access_token)
 
-    def _do_revoke(self, http, token):
+    def _do_revoke(self, http_request, token):
         """Revokes this credential and deletes the stored copy (if it exists).
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          refresh request.
             token: A string used as the token to be revoked. Can be either an
                    access_token or refresh_token.
 
@@ -840,13 +859,8 @@
         """
         logger.info('Revoking token')
         query_params = {'token': token}
-        token_revoke_uri = _helpers.update_query_params(
-            self.revoke_uri, query_params)
-        resp, content = transport.request(http, token_revoke_uri)
-        if resp.status == http_client.METHOD_NOT_ALLOWED:
-            body = urllib.parse.urlencode(query_params)
-            resp, content = transport.request(http, token_revoke_uri,
-                                              method='POST', body=body)
+        token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
+        resp, content = http_request(token_revoke_uri)
         if resp.status == http_client.OK:
             self.invalid = True
         else:
@@ -862,19 +876,23 @@
         if self.store:
             self.store.delete()
 
-    def _retrieve_scopes(self, http):
+    def _retrieve_scopes(self, http_request):
         """Retrieves the list of authorized scopes from the OAuth2 provider.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          revoke request.
         """
-        self._do_retrieve_scopes(http, self.access_token)
+        self._do_retrieve_scopes(http_request, self.access_token)
 
-    def _do_retrieve_scopes(self, http, token):
+    def _do_retrieve_scopes(self, http_request, token):
         """Retrieves the list of authorized scopes from the OAuth2 provider.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          refresh request.
             token: A string used as the token to identify the credentials to
                    the provider.
 
@@ -884,13 +902,13 @@
         """
         logger.info('Refreshing scopes')
         query_params = {'access_token': token, 'fields': 'scope'}
-        token_info_uri = _helpers.update_query_params(
-            self.token_info_uri, query_params)
-        resp, content = transport.request(http, token_info_uri)
+        token_info_uri = _update_query_params(self.token_info_uri,
+                                              query_params)
+        resp, content = http_request(token_info_uri)
         content = _helpers._from_bytes(content)
         if resp.status == http_client.OK:
             d = json.loads(content)
-            self.scopes = set(_helpers.string_to_scopes(d.get('scope', '')))
+            self.scopes = set(util.string_to_scopes(d.get('scope', '')))
         else:
             error_msg = 'Invalid response {0}.'.format(resp.status)
             try:
@@ -959,25 +977,19 @@
             data['user_agent'])
         return retval
 
-    def _refresh(self, http):
-        """Refreshes the access token.
-
-        Args:
-            http: unused HTTP object.
-
-        Raises:
-            AccessTokenCredentialsError: always
-        """
+    def _refresh(self, http_request):
         raise AccessTokenCredentialsError(
             'The access_token is expired or invalid and can\'t be refreshed.')
 
-    def _revoke(self, http):
+    def _revoke(self, http_request):
         """Revokes the access_token and deletes the store if available.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          revoke request.
         """
-        self._do_revoke(http, self.access_token)
+        self._do_revoke(http_request, self.access_token)
 
 
 def _detect_gce_environment():
@@ -993,16 +1005,21 @@
     #       could lead to false negatives in the event that we are on GCE, but
     #       the metadata resolution was particularly slow. The latter case is
     #       "unlikely".
-    http = transport.get_http_object(timeout=GCE_METADATA_TIMEOUT)
+    connection = six.moves.http_client.HTTPConnection(
+        _GCE_METADATA_HOST, timeout=GCE_METADATA_TIMEOUT)
+
     try:
-        response, _ = transport.request(
-            http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
-        return (
-            response.status == http_client.OK and
-            response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
+        headers = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
+        connection.request('GET', '/', headers=headers)
+        response = connection.getresponse()
+        if response.status == http_client.OK:
+            return (response.getheader(_METADATA_FLAVOR_HEADER) ==
+                    _DESIRED_METADATA_FLAVOR)
     except socket.error:  # socket.timeout or socket.error(64, 'Host is down')
         logger.info('Timeout attempting to reach GCE metadata service.')
         return False
+    finally:
+        connection.close()
 
 
 def _in_gae_environment():
@@ -1452,7 +1469,7 @@
     AssertionCredentials objects may be safely pickled and unpickled.
     """
 
-    @_helpers.positional(2)
+    @util.positional(2)
     def __init__(self, assertion_type, user_agent=None,
                  token_uri=oauth2client.GOOGLE_TOKEN_URI,
                  revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
@@ -1494,13 +1511,15 @@
         """Generate assertion string to be used in the access token request."""
         raise NotImplementedError
 
-    def _revoke(self, http):
+    def _revoke(self, http_request):
         """Revokes the access_token and deletes the store if available.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          revoke request.
         """
-        self._do_revoke(http, self.access_token)
+        self._do_revoke(http_request, self.access_token)
 
     def sign_blob(self, blob):
         """Cryptographically sign a blob (of bytes).
@@ -1526,7 +1545,7 @@
         raise CryptoUnavailableError('No crypto library available')
 
 
-@_helpers.positional(2)
+@util.positional(2)
 def verify_id_token(id_token, audience, http=None,
                     cert_uri=ID_TOKEN_VERIFICATION_CERTS):
     """Verifies a signed JWT id_token.
@@ -1553,7 +1572,7 @@
     if http is None:
         http = transport.get_cached_http()
 
-    resp, content = transport.request(http, cert_uri)
+    resp, content = http.request(cert_uri)
     if resp.status == http_client.OK:
         certs = json.loads(_helpers._from_bytes(content))
         return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
@@ -1605,7 +1624,7 @@
     except Exception:
         # different JSON libs raise different exceptions,
         # so we just do a catch-all here
-        resp = _helpers.parse_unique_urlencoded(content)
+        resp = dict(urllib.parse.parse_qsl(content))
 
     # some providers respond with 'expires', others with 'expires_in'
     if resp and 'expires' in resp:
@@ -1614,7 +1633,7 @@
     return resp
 
 
-@_helpers.positional(4)
+@util.positional(4)
 def credentials_from_code(client_id, client_secret, scope, code,
                           redirect_uri='postmessage', http=None,
                           user_agent=None,
@@ -1622,9 +1641,7 @@
                           auth_uri=oauth2client.GOOGLE_AUTH_URI,
                           revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
                           device_uri=oauth2client.GOOGLE_DEVICE_URI,
-                          token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
-                          pkce=False,
-                          code_verifier=None):
+                          token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI):
     """Exchanges an authorization code for an OAuth2Credentials object.
 
     Args:
@@ -1648,15 +1665,6 @@
         device_uri: string, URI for device authorization endpoint. For
                     convenience defaults to Google's endpoints but any OAuth
                     2.0 provider can be used.
-        pkce: boolean, default: False, Generate and include a "Proof Key
-              for Code Exchange" (PKCE) with your authorization and token
-              requests. This adds security for installed applications that
-              cannot protect a client_secret. See RFC 7636 for details.
-        code_verifier: bytestring or None, default: None, parameter passed
-                       as part of the code exchange when pkce=True. If
-                       None, a code_verifier will automatically be
-                       generated as part of step1_get_authorize_url(). See
-                       RFC 7636 for details.
 
     Returns:
         An OAuth2Credentials object.
@@ -1667,20 +1675,16 @@
     """
     flow = OAuth2WebServerFlow(client_id, client_secret, scope,
                                redirect_uri=redirect_uri,
-                               user_agent=user_agent,
-                               auth_uri=auth_uri,
-                               token_uri=token_uri,
-                               revoke_uri=revoke_uri,
+                               user_agent=user_agent, auth_uri=auth_uri,
+                               token_uri=token_uri, revoke_uri=revoke_uri,
                                device_uri=device_uri,
-                               token_info_uri=token_info_uri,
-                               pkce=pkce,
-                               code_verifier=code_verifier)
+                               token_info_uri=token_info_uri)
 
     credentials = flow.step2_exchange(code, http=http)
     return credentials
 
 
-@_helpers.positional(3)
+@util.positional(3)
 def credentials_from_clientsecrets_and_code(filename, scope, code,
                                             message=None,
                                             redirect_uri='postmessage',
@@ -1709,15 +1713,6 @@
         cache: An optional cache service client that implements get() and set()
                methods. See clientsecrets.loadfile() for details.
         device_uri: string, OAuth 2.0 device authorization endpoint
-        pkce: boolean, default: False, Generate and include a "Proof Key
-              for Code Exchange" (PKCE) with your authorization and token
-              requests. This adds security for installed applications that
-              cannot protect a client_secret. See RFC 7636 for details.
-        code_verifier: bytestring or None, default: None, parameter passed
-                       as part of the code exchange when pkce=True. If
-                       None, a code_verifier will automatically be
-                       generated as part of step1_get_authorize_url(). See
-                       RFC 7636 for details.
 
     Returns:
         An OAuth2Credentials object.
@@ -1808,7 +1803,7 @@
     OAuth2WebServerFlow objects may be safely pickled and unpickled.
     """
 
-    @_helpers.positional(4)
+    @util.positional(4)
     def __init__(self, client_id,
                  client_secret=None,
                  scope=None,
@@ -1821,8 +1816,6 @@
                  device_uri=oauth2client.GOOGLE_DEVICE_URI,
                  token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
                  authorization_header=None,
-                 pkce=False,
-                 code_verifier=None,
                  **kwargs):
         """Constructor for OAuth2WebServerFlow.
 
@@ -1860,15 +1853,6 @@
                                   require a client to authenticate using a
                                   header value instead of passing client_secret
                                   in the POST body.
-            pkce: boolean, default: False, Generate and include a "Proof Key
-                  for Code Exchange" (PKCE) with your authorization and token
-                  requests. This adds security for installed applications that
-                  cannot protect a client_secret. See RFC 7636 for details.
-            code_verifier: bytestring or None, default: None, parameter passed
-                           as part of the code exchange when pkce=True. If
-                           None, a code_verifier will automatically be
-                           generated as part of step1_get_authorize_url(). See
-                           RFC 7636 for details.
             **kwargs: dict, The keyword arguments are all optional and required
                       parameters for the OAuth calls.
         """
@@ -1878,7 +1862,7 @@
             raise TypeError("The value of scope must not be None")
         self.client_id = client_id
         self.client_secret = client_secret
-        self.scope = _helpers.scopes_to_string(scope)
+        self.scope = util.scopes_to_string(scope)
         self.redirect_uri = redirect_uri
         self.login_hint = login_hint
         self.user_agent = user_agent
@@ -1888,11 +1872,9 @@
         self.device_uri = device_uri
         self.token_info_uri = token_info_uri
         self.authorization_header = authorization_header
-        self._pkce = pkce
-        self.code_verifier = code_verifier
         self.params = _oauth2_web_server_flow_params(kwargs)
 
-    @_helpers.positional(1)
+    @util.positional(1)
     def step1_get_authorize_url(self, redirect_uri=None, state=None):
         """Returns a URI to redirect to the provider.
 
@@ -1930,17 +1912,10 @@
             query_params['state'] = state
         if self.login_hint is not None:
             query_params['login_hint'] = self.login_hint
-        if self._pkce:
-            if not self.code_verifier:
-                self.code_verifier = _pkce.code_verifier()
-            challenge = _pkce.code_challenge(self.code_verifier)
-            query_params['code_challenge'] = challenge
-            query_params['code_challenge_method'] = 'S256'
-
         query_params.update(self.params)
-        return _helpers.update_query_params(self.auth_uri, query_params)
+        return _update_query_params(self.auth_uri, query_params)
 
-    @_helpers.positional(1)
+    @util.positional(1)
     def step1_get_device_and_user_codes(self, http=None):
         """Returns a user code and the verification URL where to enter it
 
@@ -1965,8 +1940,8 @@
         if http is None:
             http = transport.get_http_object()
 
-        resp, content = transport.request(
-            http, self.device_uri, method='POST', body=body, headers=headers)
+        resp, content = http.request(self.device_uri, method='POST', body=body,
+                                     headers=headers)
         content = _helpers._from_bytes(content)
         if resp.status == http_client.OK:
             try:
@@ -1988,7 +1963,7 @@
                 pass
             raise OAuth2DeviceCodeError(error_msg)
 
-    @_helpers.positional(2)
+    @util.positional(2)
     def step2_exchange(self, code=None, http=None, device_flow_info=None):
         """Exchanges a code for OAuth2Credentials.
 
@@ -2031,8 +2006,6 @@
         }
         if self.client_secret is not None:
             post_data['client_secret'] = self.client_secret
-        if self._pkce:
-            post_data['code_verifier'] = self.code_verifier
         if device_flow_info is not None:
             post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
         else:
@@ -2050,8 +2023,8 @@
         if http is None:
             http = transport.get_http_object()
 
-        resp, content = transport.request(
-            http, self.token_uri, method='POST', body=body, headers=headers)
+        resp, content = http.request(self.token_uri, method='POST', body=body,
+                                     headers=headers)
         d = _parse_exchange_token_response(content)
         if resp.status == http_client.OK and 'access_token' in d:
             access_token = d['access_token']
@@ -2066,17 +2039,15 @@
                 token_expiry = delta + _UTCNOW()
 
             extracted_id_token = None
-            id_token_jwt = None
             if 'id_token' in d:
                 extracted_id_token = _extract_id_token(d['id_token'])
-                id_token_jwt = d['id_token']
 
             logger.info('Successfully retrieved access token')
             return OAuth2Credentials(
                 access_token, self.client_id, self.client_secret,
                 refresh_token, token_expiry, self.token_uri, self.user_agent,
                 revoke_uri=self.revoke_uri, id_token=extracted_id_token,
-                id_token_jwt=id_token_jwt, token_response=d, scopes=self.scope,
+                token_response=d, scopes=self.scope,
                 token_info_uri=self.token_info_uri)
         else:
             logger.info('Failed to retrieve access token: %s', content)
@@ -2089,11 +2060,10 @@
             raise FlowExchangeError(error_msg)
 
 
-@_helpers.positional(2)
+@util.positional(2)
 def flow_from_clientsecrets(filename, scope, redirect_uri=None,
                             message=None, cache=None, login_hint=None,
-                            device_uri=None, pkce=None, code_verifier=None,
-                            prompt=None):
+                            device_uri=None):
     """Create a Flow from a clientsecrets file.
 
     Will create the right kind of Flow based on the contents of the
@@ -2142,17 +2112,10 @@
                 'login_hint': login_hint,
             }
             revoke_uri = client_info.get('revoke_uri')
-            optional = (
-                'revoke_uri',
-                'device_uri',
-                'pkce',
-                'code_verifier',
-                'prompt'
-            )
-            for param in optional:
-                if locals()[param] is not None:
-                    constructor_kwargs[param] = locals()[param]
-
+            if revoke_uri is not None:
+                constructor_kwargs['revoke_uri'] = revoke_uri
+            if device_uri is not None:
+                constructor_kwargs['device_uri'] = device_uri
             return OAuth2WebServerFlow(
                 client_info['client_id'], client_info['client_secret'],
                 scope, **constructor_kwargs)
diff --git a/oauth2client/clientsecrets.py b/oauth2client/clientsecrets.py
index 1598142..4b43e66 100644
--- a/oauth2client/clientsecrets.py
+++ b/oauth2client/clientsecrets.py
@@ -22,6 +22,7 @@
 
 import six
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
 # Properties that make a client_secrets.json file valid.
 TYPE_WEB = 'web'
diff --git a/oauth2client/contrib/_fcntl_opener.py b/oauth2client/contrib/_fcntl_opener.py
new file mode 100644
index 0000000..ae6c85b
--- /dev/null
+++ b/oauth2client/contrib/_fcntl_opener.py
@@ -0,0 +1,81 @@
+# Copyright 2016 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.
+
+import errno
+import fcntl
+import time
+
+from oauth2client.contrib import locked_file
+
+
+class _FcntlOpener(locked_file._Opener):
+    """Open, lock, and unlock a file using fcntl.lockf."""
+
+    def open_and_lock(self, timeout, delay):
+        """Open the file and lock it.
+
+        Args:
+            timeout: float, How long to try to lock for.
+            delay: float, How long to wait between retries
+
+        Raises:
+            AlreadyLockedException: if the lock is already acquired.
+            IOError: if the open fails.
+            CredentialsFileSymbolicLinkError: if the file is a symbolic
+                                              link.
+        """
+        if self._locked:
+            raise locked_file.AlreadyLockedException(
+                'File {0} is already locked'.format(self._filename))
+        start_time = time.time()
+
+        locked_file.validate_file(self._filename)
+        try:
+            self._fh = open(self._filename, self._mode)
+        except IOError as e:
+            # If we can't access with _mode, try _fallback_mode and
+            # don't lock.
+            if e.errno in (errno.EPERM, errno.EACCES):
+                self._fh = open(self._filename, self._fallback_mode)
+                return
+
+        # We opened in _mode, try to lock the file.
+        while True:
+            try:
+                fcntl.lockf(self._fh.fileno(), fcntl.LOCK_EX)
+                self._locked = True
+                return
+            except IOError as e:
+                # If not retrying, then just pass on the error.
+                if timeout == 0:
+                    raise
+                if e.errno != errno.EACCES:
+                    raise
+                # We could not acquire the lock. Try again.
+                if (time.time() - start_time) >= timeout:
+                    locked_file.logger.warn('Could not lock %s in %s seconds',
+                                            self._filename, timeout)
+                    if self._fh:
+                        self._fh.close()
+                    self._fh = open(self._filename, self._fallback_mode)
+                    return
+                time.sleep(delay)
+
+    def unlock_and_close(self):
+        """Close and unlock the file using the fcntl.lockf primitive."""
+        if self._locked:
+            fcntl.lockf(self._fh.fileno(), fcntl.LOCK_UN)
+        self._locked = False
+        if self._fh:
+            self._fh.close()
diff --git a/oauth2client/contrib/_metadata.py b/oauth2client/contrib/_metadata.py
index 564cd39..10e6a69 100644
--- a/oauth2client/contrib/_metadata.py
+++ b/oauth2client/contrib/_metadata.py
@@ -19,28 +19,29 @@
 
 import datetime
 import json
-import os
 
+import httplib2
 from six.moves import http_client
 from six.moves.urllib import parse as urlparse
 
 from oauth2client import _helpers
 from oauth2client import client
-from oauth2client import transport
+from oauth2client import util
 
 
-METADATA_ROOT = 'http://{}/computeMetadata/v1/'.format(
-    os.getenv('GCE_METADATA_ROOT', 'metadata.google.internal'))
+METADATA_ROOT = 'http://metadata.google.internal/computeMetadata/v1/'
 METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
 
 
-def get(http, path, root=METADATA_ROOT, recursive=None):
+def get(http_request, path, root=METADATA_ROOT, recursive=None):
     """Fetch a resource from the metadata server.
 
     Args:
-        http: an object to be used to make HTTP requests.
         path: A string indicating the resource to retrieve. For example,
-            'instance/service-accounts/default'
+            'instance/service-accounts/defualt'
+        http_request: A callable that matches the method
+            signature of httplib2.Http.request. Used to make the request to the
+            metadataserver.
         root: A string indicating the full path to the metadata server root.
         recursive: A boolean indicating whether to do a recursive query of
             metadata. See
@@ -50,14 +51,15 @@
         A dictionary if the metadata server returns JSON, otherwise a string.
 
     Raises:
-        http_client.HTTPException if an error corrured while
-        retrieving metadata.
+        httplib2.Httplib2Error if an error corrured while retrieving metadata.
     """
     url = urlparse.urljoin(root, path)
-    url = _helpers._add_query_parameter(url, 'recursive', recursive)
+    url = util._add_query_parameter(url, 'recursive', recursive)
 
-    response, content = transport.request(
-        http, url, headers=METADATA_HEADERS)
+    response, content = http_request(
+        url,
+        headers=METADATA_HEADERS
+    )
 
     if response.status == http_client.OK:
         decoded = _helpers._from_bytes(content)
@@ -66,20 +68,21 @@
         else:
             return decoded
     else:
-        raise http_client.HTTPException(
+        raise httplib2.HttpLib2Error(
             'Failed to retrieve {0} from the Google Compute Engine'
             'metadata service. Response:\n{1}'.format(url, response))
 
 
-def get_service_account_info(http, service_account='default'):
+def get_service_account_info(http_request, service_account='default'):
     """Get information about a service account from the metadata server.
 
     Args:
-        http: an object to be used to make HTTP requests.
         service_account: An email specifying the service account for which to
             look up information. Default will be information for the "default"
             service account of the current compute engine instance.
-
+        http_request: A callable that matches the method
+            signature of httplib2.Http.request. Used to make the request to the
+            metadata server.
     Returns:
          A dictionary with information about the specified service account,
          for example:
@@ -91,19 +94,21 @@
             }
     """
     return get(
-        http,
+        http_request,
         'instance/service-accounts/{0}/'.format(service_account),
         recursive=True)
 
 
-def get_token(http, service_account='default'):
+def get_token(http_request, service_account='default'):
     """Fetch an oauth token for the
 
     Args:
-        http: an object to be used to make HTTP requests.
         service_account: An email specifying the service account this token
             should represent. Default will be a token for the "default" service
             account of the current compute engine instance.
+        http_request: A callable that matches the method
+            signature of httplib2.Http.request. Used to make the request to the
+            metadataserver.
 
     Returns:
          A tuple of (access token, token expiration), where access token is the
@@ -111,7 +116,7 @@
          that indicates when the access token will expire.
     """
     token_json = get(
-        http,
+        http_request,
         'instance/service-accounts/{0}/token'.format(service_account))
     token_expiry = client._UTCNOW() + datetime.timedelta(
         seconds=token_json['expires_in'])
diff --git a/oauth2client/contrib/_win32_opener.py b/oauth2client/contrib/_win32_opener.py
new file mode 100644
index 0000000..34b4f48
--- /dev/null
+++ b/oauth2client/contrib/_win32_opener.py
@@ -0,0 +1,106 @@
+# Copyright 2016 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.
+
+import errno
+import time
+
+import pywintypes
+import win32con
+import win32file
+
+from oauth2client.contrib import locked_file
+
+
+class _Win32Opener(locked_file._Opener):
+    """Open, lock, and unlock a file using windows primitives."""
+
+    # Error #33:
+    #  'The process cannot access the file because another process'
+    FILE_IN_USE_ERROR = 33
+
+    # Error #158:
+    #  'The segment is already unlocked.'
+    FILE_ALREADY_UNLOCKED_ERROR = 158
+
+    def open_and_lock(self, timeout, delay):
+        """Open the file and lock it.
+
+        Args:
+            timeout: float, How long to try to lock for.
+            delay: float, How long to wait between retries
+
+        Raises:
+            AlreadyLockedException: if the lock is already acquired.
+            IOError: if the open fails.
+            CredentialsFileSymbolicLinkError: if the file is a symbolic
+                                              link.
+        """
+        if self._locked:
+            raise locked_file.AlreadyLockedException(
+                'File {0} is already locked'.format(self._filename))
+        start_time = time.time()
+
+        locked_file.validate_file(self._filename)
+        try:
+            self._fh = open(self._filename, self._mode)
+        except IOError as e:
+            # If we can't access with _mode, try _fallback_mode
+            # and don't lock.
+            if e.errno == errno.EACCES:
+                self._fh = open(self._filename, self._fallback_mode)
+                return
+
+        # We opened in _mode, try to lock the file.
+        while True:
+            try:
+                hfile = win32file._get_osfhandle(self._fh.fileno())
+                win32file.LockFileEx(
+                    hfile,
+                    (win32con.LOCKFILE_FAIL_IMMEDIATELY |
+                     win32con.LOCKFILE_EXCLUSIVE_LOCK), 0, -0x10000,
+                    pywintypes.OVERLAPPED())
+                self._locked = True
+                return
+            except pywintypes.error as e:
+                if timeout == 0:
+                    raise
+
+                # If the error is not that the file is already
+                # in use, raise.
+                if e[0] != _Win32Opener.FILE_IN_USE_ERROR:
+                    raise
+
+                # We could not acquire the lock. Try again.
+                if (time.time() - start_time) >= timeout:
+                    locked_file.logger.warn('Could not lock %s in %s seconds',
+                                            self._filename, timeout)
+                    if self._fh:
+                        self._fh.close()
+                    self._fh = open(self._filename, self._fallback_mode)
+                    return
+                time.sleep(delay)
+
+    def unlock_and_close(self):
+        """Close and unlock the file using the win32 primitive."""
+        if self._locked:
+            try:
+                hfile = win32file._get_osfhandle(self._fh.fileno())
+                win32file.UnlockFileEx(hfile, 0, -0x10000,
+                                       pywintypes.OVERLAPPED())
+            except pywintypes.error as e:
+                if e[0] != _Win32Opener.FILE_ALREADY_UNLOCKED_ERROR:
+                    raise
+        self._locked = False
+        if self._fh:
+            self._fh.close()
diff --git a/oauth2client/contrib/appengine.py b/oauth2client/contrib/appengine.py
index c1326ee..661105e 100644
--- a/oauth2client/contrib/appengine.py
+++ b/oauth2client/contrib/appengine.py
@@ -29,13 +29,13 @@
 from google.appengine.api import users
 from google.appengine.ext import db
 from google.appengine.ext.webapp.util import login_required
+import httplib2
 import webapp2 as webapp
 
 import oauth2client
-from oauth2client import _helpers
 from oauth2client import client
 from oauth2client import clientsecrets
-from oauth2client import transport
+from oauth2client import util
 from oauth2client.contrib import xsrfutil
 
 # This is a temporary fix for a Google internal issue.
@@ -45,6 +45,8 @@
     _appengine_ndb = None
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
 logger = logging.getLogger(__name__)
 
 OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
@@ -129,7 +131,7 @@
     information to generate and refresh its own access tokens.
     """
 
-    @_helpers.positional(2)
+    @util.positional(2)
     def __init__(self, scope, **kwargs):
         """Constructor for AppAssertionCredentials
 
@@ -141,7 +143,7 @@
                                 or unspecified, the default service account for
                                 the app is used.
         """
-        self.scope = _helpers.scopes_to_string(scope)
+        self.scope = util.scopes_to_string(scope)
         self._kwargs = kwargs
         self.service_account_id = kwargs.get('service_account_id', None)
         self._service_account_email = None
@@ -155,15 +157,17 @@
         data = json.loads(json_data)
         return AppAssertionCredentials(data['scope'])
 
-    def _refresh(self, http):
-        """Refreshes the access token.
+    def _refresh(self, http_request):
+        """Refreshes the access_token.
 
         Since the underlying App Engine app_identity implementation does its
         own caching we can skip all the storage hoops and just to a refresh
         using the API.
 
         Args:
-            http: unused HTTP object
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          refresh request.
 
         Raises:
             AccessTokenRefreshError: When the refresh fails.
@@ -301,7 +305,7 @@
     and that entities are stored by key_name.
     """
 
-    @_helpers.positional(4)
+    @util.positional(4)
     def __init__(self, model, key_name, property_name, cache=None, user=None):
         """Constructor for Storage.
 
@@ -519,7 +523,7 @@
 
     flow = property(get_flow, set_flow)
 
-    @_helpers.positional(4)
+    @util.positional(4)
     def __init__(self, client_id, client_secret, scope,
                  auth_uri=oauth2client.GOOGLE_AUTH_URI,
                  token_uri=oauth2client.GOOGLE_TOKEN_URI,
@@ -586,7 +590,7 @@
         self.credentials = None
         self._client_id = client_id
         self._client_secret = client_secret
-        self._scope = _helpers.scopes_to_string(scope)
+        self._scope = util.scopes_to_string(scope)
         self._auth_uri = auth_uri
         self._token_uri = token_uri
         self._revoke_uri = revoke_uri
@@ -738,8 +742,7 @@
             *args: Positional arguments passed to httplib2.Http constructor.
             **kwargs: Positional arguments passed to httplib2.Http constructor.
         """
-        return self.credentials.authorize(
-            transport.get_http_object(*args, **kwargs))
+        return self.credentials.authorize(httplib2.Http(*args, **kwargs))
 
     @property
     def callback_path(self):
@@ -801,7 +804,7 @@
                     if (decorator._token_response_param and
                             credentials.token_response):
                         resp_json = json.dumps(credentials.token_response)
-                        redirect_uri = _helpers._add_query_parameter(
+                        redirect_uri = util._add_query_parameter(
                             redirect_uri, decorator._token_response_param,
                             resp_json)
 
@@ -845,7 +848,7 @@
 
     """
 
-    @_helpers.positional(3)
+    @util.positional(3)
     def __init__(self, filename, scope, message=None, cache=None, **kwargs):
         """Constructor
 
@@ -888,7 +891,7 @@
             self._message = 'Please configure your application for OAuth 2.0.'
 
 
-@_helpers.positional(2)
+@util.positional(2)
 def oauth2decorator_from_clientsecrets(filename, scope,
                                        message=None, cache=None):
     """Creates an OAuth2Decorator populated from a clientsecrets file.
diff --git a/oauth2client/contrib/devshell.py b/oauth2client/contrib/devshell.py
index 691765f..b8bb978 100644
--- a/oauth2client/contrib/devshell.py
+++ b/oauth2client/contrib/devshell.py
@@ -37,7 +37,6 @@
 class NoDevshellServer(Error):
     """Error when no Developer Shell server can be contacted."""
 
-
 # The request for credential information to the Developer Shell client socket
 # is always an empty PBLite-formatted JSON object, so just define it as a
 # constant.
@@ -118,12 +117,7 @@
             user_agent)
         self._refresh(None)
 
-    def _refresh(self, http):
-        """Refreshes the access token.
-
-        Args:
-            http: unused HTTP object
-        """
+    def _refresh(self, http_request):
         self.devshell_response = _SendRecv()
         self.access_token = self.devshell_response.access_token
         expires_in = self.devshell_response.expires_in
diff --git a/oauth2client/contrib/django_util/__init__.py b/oauth2client/contrib/django_util/__init__.py
index 644a8f9..5449e32 100644
--- a/oauth2client/contrib/django_util/__init__.py
+++ b/oauth2client/contrib/django_util/__init__.py
@@ -52,9 +52,6 @@
 
 This helper also requires the Django Session Middleware, so
 ``django.contrib.sessions.middleware`` should be in INSTALLED_APPS as well.
-MIDDLEWARE or MIDDLEWARE_CLASSES (in Django  versions <1.10) should also
-contain the string 'django.contrib.sessions.middleware.SessionMiddleware'.
-
 
 Add the client secrets created earlier to the settings. You can either
 specify the path to the credentials file in JSON format
@@ -231,10 +228,10 @@
 import django.conf
 from django.core import exceptions
 from django.core import urlresolvers
+import httplib2
 from six.moves.urllib import parse
 
 from oauth2client import clientsecrets
-from oauth2client import transport
 from oauth2client.contrib import dictionary_storage
 from oauth2client.contrib.django_util import storage
 
@@ -338,26 +335,16 @@
         self.request_prefix = getattr(settings_instance,
                                       'GOOGLE_OAUTH2_REQUEST_ATTRIBUTE',
                                       GOOGLE_OAUTH2_REQUEST_ATTRIBUTE)
-        info = _get_oauth2_client_id_and_secret(settings_instance)
-        self.client_id, self.client_secret = info
+        self.client_id, self.client_secret = \
+            _get_oauth2_client_id_and_secret(settings_instance)
 
-        # Django 1.10 deprecated MIDDLEWARE_CLASSES in favor of MIDDLEWARE
-        middleware_settings = getattr(settings_instance, 'MIDDLEWARE', None)
-        if middleware_settings is None:
-            middleware_settings = getattr(
-                settings_instance, 'MIDDLEWARE_CLASSES', None)
-        if middleware_settings is None:
+        if ('django.contrib.sessions.middleware.SessionMiddleware'
+           not in settings_instance.MIDDLEWARE_CLASSES):
             raise exceptions.ImproperlyConfigured(
-                'Django settings has neither MIDDLEWARE nor MIDDLEWARE_CLASSES'
-                'configured')
-
-        if ('django.contrib.sessions.middleware.SessionMiddleware' not in
-                middleware_settings):
-            raise exceptions.ImproperlyConfigured(
-                'The Google OAuth2 Helper requires session middleware to '
-                'be installed. Edit your MIDDLEWARE_CLASSES or MIDDLEWARE '
-                'setting to include \'django.contrib.sessions.middleware.'
-                'SessionMiddleware\'.')
+                  'The Google OAuth2 Helper requires session middleware to '
+                  'be installed. Edit your MIDDLEWARE_CLASSES setting'
+                  ' to include \'django.contrib.sessions.middleware.'
+                  'SessionMiddleware\'.')
         (self.storage_model, self.storage_model_user_property,
          self.storage_model_credentials_property) = _get_storage_model()
 
@@ -483,7 +470,8 @@
 
     @property
     def http(self):
-        """Helper: create HTTP client authorized with OAuth2 credentials."""
+        """Helper method to create an HTTP client authorized with OAuth2
+        credentials."""
         if self.has_credentials():
-            return self.credentials.authorize(transport.get_http_object())
+            return self.credentials.authorize(httplib2.Http())
         return None
diff --git a/oauth2client/contrib/django_util/models.py b/oauth2client/contrib/django_util/models.py
index 37cc697..87e1da7 100644
--- a/oauth2client/contrib/django_util/models.py
+++ b/oauth2client/contrib/django_util/models.py
@@ -19,7 +19,6 @@
 
 from django.db import models
 from django.utils import encoding
-import jsonpickle
 
 import oauth2client
 
@@ -49,12 +48,7 @@
         elif isinstance(value, oauth2client.client.Credentials):
             return value
         else:
-            try:
-                return jsonpickle.decode(
-                    base64.b64decode(encoding.smart_bytes(value)).decode())
-            except ValueError:
-                return pickle.loads(
-                    base64.b64decode(encoding.smart_bytes(value)))
+            return pickle.loads(base64.b64decode(encoding.smart_bytes(value)))
 
     def get_prep_value(self, value):
         """Overrides ``models.Field`` method. This is used to convert
@@ -64,8 +58,7 @@
         if value is None:
             return None
         else:
-            return encoding.smart_text(
-                base64.b64encode(jsonpickle.encode(value).encode()))
+            return encoding.smart_text(base64.b64encode(pickle.dumps(value)))
 
     def value_to_string(self, obj):
         """Convert the field value from the provided model to a string.
diff --git a/oauth2client/contrib/django_util/views.py b/oauth2client/contrib/django_util/views.py
index 1835208..4d8ae03 100644
--- a/oauth2client/contrib/django_util/views.py
+++ b/oauth2client/contrib/django_util/views.py
@@ -22,14 +22,13 @@
 import hashlib
 import json
 import os
+import pickle
 
 from django import http
 from django import shortcuts
 from django.conf import settings
 from django.core import urlresolvers
 from django.shortcuts import redirect
-from django.utils import html
-import jsonpickle
 from six.moves.urllib import parse
 
 from oauth2client import client
@@ -72,7 +71,7 @@
             urlresolvers.reverse("google_oauth:callback")))
 
     flow_key = _FLOW_KEY.format(csrf_token)
-    request.session[flow_key] = jsonpickle.encode(flow)
+    request.session[flow_key] = pickle.dumps(flow)
     return flow
 
 
@@ -90,7 +89,7 @@
         CSRF token.
     """
     flow_pickle = request.session.get(_FLOW_KEY.format(csrf_token), None)
-    return None if flow_pickle is None else jsonpickle.decode(flow_pickle)
+    return None if flow_pickle is None else pickle.loads(flow_pickle)
 
 
 def oauth2_callback(request):
@@ -110,7 +109,6 @@
     if 'error' in request.GET:
         reason = request.GET.get(
             'error_description', request.GET.get('error', ''))
-        reason = html.escape(reason)
         return http.HttpResponseBadRequest(
             'Authorization failed {0}'.format(reason))
 
@@ -172,10 +170,7 @@
          A redirect to Google OAuth2 Authorization.
     """
     return_url = request.GET.get('return_url', None)
-    if not return_url:
-        return_url = request.META.get('HTTP_REFERER', '/')
 
-    scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes)
     # Model storage (but not session storage) requires a logged in user
     if django_util.oauth2_settings.storage_model:
         if not request.user.is_authenticated():
@@ -183,11 +178,13 @@
                 settings.LOGIN_URL, parse.quote(request.get_full_path())))
         # This checks for the case where we ended up here because of a logged
         # out user but we had credentials for it in the first place
-        else:
-            user_oauth = django_util.UserOAuth2(request, scopes, return_url)
-            if user_oauth.has_credentials():
-                return redirect(return_url)
+        elif get_storage(request).get() is not None:
+            return redirect(return_url)
 
+    scopes = request.GET.getlist('scopes', django_util.oauth2_settings.scopes)
+
+    if not return_url:
+        return_url = request.META.get('HTTP_REFERER', '/')
     flow = _make_flow(request=request, scopes=scopes, return_url=return_url)
     auth_url = flow.step1_get_authorize_url()
     return shortcuts.redirect(auth_url)
diff --git a/oauth2client/contrib/flask_util.py b/oauth2client/contrib/flask_util.py
index fabd613..47c3df1 100644
--- a/oauth2client/contrib/flask_util.py
+++ b/oauth2client/contrib/flask_util.py
@@ -176,18 +176,19 @@
     from flask import request
     from flask import session
     from flask import url_for
-    import markupsafe
 except ImportError:  # pragma: NO COVER
     raise ImportError('The flask utilities require flask 0.9 or newer.')
 
+import httplib2
 import six.moves.http_client as httplib
 
 from oauth2client import client
 from oauth2client import clientsecrets
-from oauth2client import transport
 from oauth2client.contrib import dictionary_storage
 
 
+__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
+
 _DEFAULT_SCOPES = ('email',)
 _CREDENTIALS_KEY = 'google_oauth2_credentials'
 _FLOW_KEY = 'google_oauth2_flow_{0}'
@@ -389,7 +390,6 @@
         if 'error' in request.args:
             reason = request.args.get(
                 'error_description', request.args.get('error', ''))
-            reason = markupsafe.escape(reason)
             return ('Authorization failed: {0}'.format(reason),
                     httplib.BAD_REQUEST)
 
@@ -553,5 +553,4 @@
         """
         if not self.credentials:
             raise ValueError('No credentials available.')
-        return self.credentials.authorize(
-            transport.get_http_object(*args, **kwargs))
+        return self.credentials.authorize(httplib2.Http(*args, **kwargs))
diff --git a/oauth2client/contrib/gce.py b/oauth2client/contrib/gce.py
index aaab15f..f3a6ca1 100644
--- a/oauth2client/contrib/gce.py
+++ b/oauth2client/contrib/gce.py
@@ -20,12 +20,14 @@
 import logging
 import warnings
 
-from six.moves import http_client
+import httplib2
 
 from oauth2client import client
 from oauth2client.contrib import _metadata
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
 logger = logging.getLogger(__name__)
 
 _SCOPES_WARNING = """\
@@ -96,40 +98,44 @@
         Returns:
             A set of strings containing the canonical list of scopes.
         """
-        self._retrieve_info(http)
+        self._retrieve_info(http.request)
         return self.scopes
 
-    def _retrieve_info(self, http):
-        """Retrieves service account info for invalid credentials.
+    def _retrieve_info(self, http_request):
+        """Validates invalid service accounts by retrieving service account info.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make the
+                          request to the metadata server
         """
         if self.invalid:
             info = _metadata.get_service_account_info(
-                http,
+                http_request,
                 service_account=self.service_account_email or 'default')
             self.invalid = False
             self.service_account_email = info['email']
             self.scopes = info['scopes']
 
-    def _refresh(self, http):
-        """Refreshes the access token.
+    def _refresh(self, http_request):
+        """Refreshes the access_token.
 
         Skip all the storage hoops and just refresh using the API.
 
         Args:
-            http: an object to be used to make HTTP requests.
+            http_request: callable, a callable that matches the method
+                          signature of httplib2.Http.request, used to make
+                          the refresh request.
 
         Raises:
             HttpAccessTokenRefreshError: When the refresh fails.
         """
         try:
-            self._retrieve_info(http)
+            self._retrieve_info(http_request)
             self.access_token, self.token_expiry = _metadata.get_token(
-                http, service_account=self.service_account_email)
-        except http_client.HTTPException as err:
-            raise client.HttpAccessTokenRefreshError(str(err))
+                http_request, service_account=self.service_account_email)
+        except httplib2.HttpLib2Error as e:
+            raise client.HttpAccessTokenRefreshError(str(e))
 
     @property
     def serialization_data(self):
diff --git a/oauth2client/contrib/keyring_storage.py b/oauth2client/contrib/keyring_storage.py
index 4af9448..f4f2e30 100644
--- a/oauth2client/contrib/keyring_storage.py
+++ b/oauth2client/contrib/keyring_storage.py
@@ -24,6 +24,9 @@
 from oauth2client import client
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
 class Storage(client.Storage):
     """Store and retrieve a single credential to and from the keyring.
 
diff --git a/oauth2client/contrib/locked_file.py b/oauth2client/contrib/locked_file.py
new file mode 100644
index 0000000..0d28ebb
--- /dev/null
+++ b/oauth2client/contrib/locked_file.py
@@ -0,0 +1,234 @@
+# Copyright 2014 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.
+
+"""Locked file interface that should work on Unix and Windows pythons.
+
+This module first tries to use fcntl locking to ensure serialized access
+to a file, then falls back on a lock file if that is unavialable.
+
+Usage::
+
+    f = LockedFile('filename', 'r+b', 'rb')
+    f.open_and_lock()
+    if f.is_locked():
+      print('Acquired filename with r+b mode')
+      f.file_handle().write('locked data')
+    else:
+      print('Acquired filename with rb mode')
+    f.unlock_and_close()
+
+"""
+
+from __future__ import print_function
+
+import errno
+import logging
+import os
+import time
+
+from oauth2client import util
+
+
+__author__ = 'cache@google.com (David T McWherter)'
+
+logger = logging.getLogger(__name__)
+
+
+class CredentialsFileSymbolicLinkError(Exception):
+    """Credentials files must not be symbolic links."""
+
+
+class AlreadyLockedException(Exception):
+    """Trying to lock a file that has already been locked by the LockedFile."""
+    pass
+
+
+def validate_file(filename):
+    if os.path.islink(filename):
+        raise CredentialsFileSymbolicLinkError(
+            'File: {0} is a symbolic link.'.format(filename))
+
+
+class _Opener(object):
+    """Base class for different locking primitives."""
+
+    def __init__(self, filename, mode, fallback_mode):
+        """Create an Opener.
+
+        Args:
+            filename: string, The pathname of the file.
+            mode: string, The preferred mode to access the file with.
+            fallback_mode: string, The mode to use if locking fails.
+        """
+        self._locked = False
+        self._filename = filename
+        self._mode = mode
+        self._fallback_mode = fallback_mode
+        self._fh = None
+        self._lock_fd = None
+
+    def is_locked(self):
+        """Was the file locked."""
+        return self._locked
+
+    def file_handle(self):
+        """The file handle to the file. Valid only after opened."""
+        return self._fh
+
+    def filename(self):
+        """The filename that is being locked."""
+        return self._filename
+
+    def open_and_lock(self, timeout, delay):
+        """Open the file and lock it.
+
+        Args:
+            timeout: float, How long to try to lock for.
+            delay: float, How long to wait between retries.
+        """
+        pass
+
+    def unlock_and_close(self):
+        """Unlock and close the file."""
+        pass
+
+
+class _PosixOpener(_Opener):
+    """Lock files using Posix advisory lock files."""
+
+    def open_and_lock(self, timeout, delay):
+        """Open the file and lock it.
+
+        Tries to create a .lock file next to the file we're trying to open.
+
+        Args:
+            timeout: float, How long to try to lock for.
+            delay: float, How long to wait between retries.
+
+        Raises:
+            AlreadyLockedException: if the lock is already acquired.
+            IOError: if the open fails.
+            CredentialsFileSymbolicLinkError if the file is a symbolic link.
+        """
+        if self._locked:
+            raise AlreadyLockedException(
+                'File {0} is already locked'.format(self._filename))
+        self._locked = False
+
+        validate_file(self._filename)
+        try:
+            self._fh = open(self._filename, self._mode)
+        except IOError as e:
+            # If we can't access with _mode, try _fallback_mode and don't lock.
+            if e.errno == errno.EACCES:
+                self._fh = open(self._filename, self._fallback_mode)
+                return
+
+        lock_filename = self._posix_lockfile(self._filename)
+        start_time = time.time()
+        while True:
+            try:
+                self._lock_fd = os.open(lock_filename,
+                                        os.O_CREAT | os.O_EXCL | os.O_RDWR)
+                self._locked = True
+                break
+
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+                if (time.time() - start_time) >= timeout:
+                    logger.warn('Could not acquire lock %s in %s seconds',
+                                lock_filename, timeout)
+                    # Close the file and open in fallback_mode.
+                    if self._fh:
+                        self._fh.close()
+                    self._fh = open(self._filename, self._fallback_mode)
+                    return
+                time.sleep(delay)
+
+    def unlock_and_close(self):
+        """Unlock a file by removing the .lock file, and close the handle."""
+        if self._locked:
+            lock_filename = self._posix_lockfile(self._filename)
+            os.close(self._lock_fd)
+            os.unlink(lock_filename)
+            self._locked = False
+            self._lock_fd = None
+        if self._fh:
+            self._fh.close()
+
+    def _posix_lockfile(self, filename):
+        """The name of the lock file to use for posix locking."""
+        return '{0}.lock'.format(filename)
+
+
+class LockedFile(object):
+    """Represent a file that has exclusive access."""
+
+    @util.positional(4)
+    def __init__(self, filename, mode, fallback_mode, use_native_locking=True):
+        """Construct a LockedFile.
+
+        Args:
+            filename: string, The path of the file to open.
+            mode: string, The mode to try to open the file with.
+            fallback_mode: string, The mode to use if locking fails.
+            use_native_locking: bool, Whether or not fcntl/win32 locking is
+                                used.
+        """
+        opener = None
+        if not opener and use_native_locking:
+            try:
+                from oauth2client.contrib._win32_opener import _Win32Opener
+                opener = _Win32Opener(filename, mode, fallback_mode)
+            except ImportError:
+                try:
+                    from oauth2client.contrib._fcntl_opener import _FcntlOpener
+                    opener = _FcntlOpener(filename, mode, fallback_mode)
+                except ImportError:
+                    pass
+
+        if not opener:
+            opener = _PosixOpener(filename, mode, fallback_mode)
+
+        self._opener = opener
+
+    def filename(self):
+        """Return the filename we were constructed with."""
+        return self._opener._filename
+
+    def file_handle(self):
+        """Return the file_handle to the opened file."""
+        return self._opener.file_handle()
+
+    def is_locked(self):
+        """Return whether we successfully locked the file."""
+        return self._opener.is_locked()
+
+    def open_and_lock(self, timeout=0, delay=0.05):
+        """Open the file, trying to lock it.
+
+        Args:
+            timeout: float, The number of seconds to try to acquire the lock.
+            delay: float, The number of seconds to wait between retry attempts.
+
+        Raises:
+            AlreadyLockedException: if the lock is already acquired.
+            IOError: if the open fails.
+        """
+        self._opener.open_and_lock(timeout, delay)
+
+    def unlock_and_close(self):
+        """Unlock and close a file."""
+        self._opener.unlock_and_close()
diff --git a/oauth2client/contrib/multistore_file.py b/oauth2client/contrib/multistore_file.py
new file mode 100644
index 0000000..10f4cb4
--- /dev/null
+++ b/oauth2client/contrib/multistore_file.py
@@ -0,0 +1,505 @@
+# Copyright 2014 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.
+
+"""Multi-credential file store with lock support.
+
+This module implements a JSON credential store where multiple
+credentials can be stored in one file. That file supports locking
+both in a single process and across processes.
+
+The credential themselves are keyed off of:
+
+* client_id
+* user_agent
+* scope
+
+The format of the stored data is like so::
+
+    {
+      'file_version': 1,
+      'data': [
+          {
+              'key': {
+                  'clientId': '<client id>',
+                  'userAgent': '<user agent>',
+                  'scope': '<scope>'
+              },
+              'credential': {
+                  # JSON serialized Credentials.
+              }
+          }
+      ]
+    }
+
+"""
+
+import errno
+import json
+import logging
+import os
+import threading
+
+from oauth2client import client
+from oauth2client import util
+from oauth2client.contrib import locked_file
+
+__author__ = 'jbeda@google.com (Joe Beda)'
+
+logger = logging.getLogger(__name__)
+
+logger.warning(
+    'The oauth2client.contrib.multistore_file module has been deprecated and '
+    'will be removed in the next release of oauth2client. Please migrate to '
+    'multiprocess_file_storage.')
+
+# A dict from 'filename'->_MultiStore instances
+_multistores = {}
+_multistores_lock = threading.Lock()
+
+
+class Error(Exception):
+    """Base error for this module."""
+
+
+class NewerCredentialStoreError(Error):
+    """The credential store is a newer version than supported."""
+
+
+def _dict_to_tuple_key(dictionary):
+    """Converts a dictionary to a tuple that can be used as an immutable key.
+
+    The resulting key is always sorted so that logically equivalent
+    dictionaries always produce an identical tuple for a key.
+
+    Args:
+        dictionary: the dictionary to use as the key.
+
+    Returns:
+        A tuple representing the dictionary in it's naturally sorted ordering.
+    """
+    return tuple(sorted(dictionary.items()))
+
+
+@util.positional(4)
+def get_credential_storage(filename, client_id, user_agent, scope,
+                           warn_on_readonly=True):
+    """Get a Storage instance for a credential.
+
+    Args:
+        filename: The JSON file storing a set of credentials
+        client_id: The client_id for the credential
+        user_agent: The user agent for the credential
+        scope: string or iterable of strings, Scope(s) being requested
+        warn_on_readonly: if True, log a warning if the store is readonly
+
+    Returns:
+        An object derived from client.Storage for getting/setting the
+        credential.
+    """
+    # Recreate the legacy key with these specific parameters
+    key = {'clientId': client_id, 'userAgent': user_agent,
+           'scope': util.scopes_to_string(scope)}
+    return get_credential_storage_custom_key(
+        filename, key, warn_on_readonly=warn_on_readonly)
+
+
+@util.positional(2)
+def get_credential_storage_custom_string_key(filename, key_string,
+                                             warn_on_readonly=True):
+    """Get a Storage instance for a credential using a single string as a key.
+
+    Allows you to provide a string as a custom key that will be used for
+    credential storage and retrieval.
+
+    Args:
+        filename: The JSON file storing a set of credentials
+        key_string: A string to use as the key for storing this credential.
+        warn_on_readonly: if True, log a warning if the store is readonly
+
+    Returns:
+        An object derived from client.Storage for getting/setting the
+        credential.
+    """
+    # Create a key dictionary that can be used
+    key_dict = {'key': key_string}
+    return get_credential_storage_custom_key(
+        filename, key_dict, warn_on_readonly=warn_on_readonly)
+
+
+@util.positional(2)
+def get_credential_storage_custom_key(filename, key_dict,
+                                      warn_on_readonly=True):
+    """Get a Storage instance for a credential using a dictionary as a key.
+
+    Allows you to provide a dictionary as a custom key that will be used for
+    credential storage and retrieval.
+
+    Args:
+        filename: The JSON file storing a set of credentials
+        key_dict: A dictionary to use as the key for storing this credential.
+                  There is no ordering of the keys in the dictionary. Logically
+                  equivalent dictionaries will produce equivalent storage keys.
+        warn_on_readonly: if True, log a warning if the store is readonly
+
+    Returns:
+        An object derived from client.Storage for getting/setting the
+        credential.
+    """
+    multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
+    key = _dict_to_tuple_key(key_dict)
+    return multistore._get_storage(key)
+
+
+@util.positional(1)
+def get_all_credential_keys(filename, warn_on_readonly=True):
+    """Gets all the registered credential keys in the given Multistore.
+
+    Args:
+        filename: The JSON file storing a set of credentials
+        warn_on_readonly: if True, log a warning if the store is readonly
+
+    Returns:
+        A list of the credential keys present in the file.  They are returned
+        as dictionaries that can be passed into
+        get_credential_storage_custom_key to get the actual credentials.
+    """
+    multistore = _get_multistore(filename, warn_on_readonly=warn_on_readonly)
+    multistore._lock()
+    try:
+        return multistore._get_all_credential_keys()
+    finally:
+        multistore._unlock()
+
+
+@util.positional(1)
+def _get_multistore(filename, warn_on_readonly=True):
+    """A helper method to initialize the multistore with proper locking.
+
+    Args:
+        filename: The JSON file storing a set of credentials
+        warn_on_readonly: if True, log a warning if the store is readonly
+
+    Returns:
+        A multistore object
+    """
+    filename = os.path.expanduser(filename)
+    _multistores_lock.acquire()
+    try:
+        multistore = _multistores.setdefault(
+            filename, _MultiStore(filename, warn_on_readonly=warn_on_readonly))
+    finally:
+        _multistores_lock.release()
+    return multistore
+
+
+class _MultiStore(object):
+    """A file backed store for multiple credentials."""
+
+    @util.positional(2)
+    def __init__(self, filename, warn_on_readonly=True):
+        """Initialize the class.
+
+        This will create the file if necessary.
+        """
+        self._file = locked_file.LockedFile(filename, 'r+', 'r')
+        self._thread_lock = threading.Lock()
+        self._read_only = False
+        self._warn_on_readonly = warn_on_readonly
+
+        self._create_file_if_needed()
+
+        # Cache of deserialized store. This is only valid after the
+        # _MultiStore is locked or _refresh_data_cache is called. This is
+        # of the form of:
+        #
+        # ((key, value), (key, value)...) -> OAuth2Credential
+        #
+        # If this is None, then the store hasn't been read yet.
+        self._data = None
+
+    class _Storage(client.Storage):
+        """A Storage object that can read/write a single credential."""
+
+        def __init__(self, multistore, key):
+            self._multistore = multistore
+            self._key = key
+
+        def acquire_lock(self):
+            """Acquires any lock necessary to access this Storage.
+
+            This lock is not reentrant.
+            """
+            self._multistore._lock()
+
+        def release_lock(self):
+            """Release the Storage lock.
+
+            Trying to release a lock that isn't held will result in a
+            RuntimeError.
+            """
+            self._multistore._unlock()
+
+        def locked_get(self):
+            """Retrieve credential.
+
+            The Storage lock must be held when this is called.
+
+            Returns:
+                oauth2client.client.Credentials
+            """
+            credential = self._multistore._get_credential(self._key)
+            if credential:
+                credential.set_store(self)
+            return credential
+
+        def locked_put(self, credentials):
+            """Write a credential.
+
+            The Storage lock must be held when this is called.
+
+            Args:
+                credentials: Credentials, the credentials to store.
+            """
+            self._multistore._update_credential(self._key, credentials)
+
+        def locked_delete(self):
+            """Delete a credential.
+
+            The Storage lock must be held when this is called.
+
+            Args:
+                credentials: Credentials, the credentials to store.
+            """
+            self._multistore._delete_credential(self._key)
+
+    def _create_file_if_needed(self):
+        """Create an empty file if necessary.
+
+        This method will not initialize the file. Instead it implements a
+        simple version of "touch" to ensure the file has been created.
+        """
+        if not os.path.exists(self._file.filename()):
+            old_umask = os.umask(0o177)
+            try:
+                open(self._file.filename(), 'a+b').close()
+            finally:
+                os.umask(old_umask)
+
+    def _lock(self):
+        """Lock the entire multistore."""
+        self._thread_lock.acquire()
+        try:
+            self._file.open_and_lock()
+        except (IOError, OSError) as e:
+            if e.errno == errno.ENOSYS:
+                logger.warn('File system does not support locking the '
+                            'credentials file.')
+            elif e.errno == errno.ENOLCK:
+                logger.warn('File system is out of resources for writing the '
+                            'credentials file (is your disk full?).')
+            elif e.errno == errno.EDEADLK:
+                logger.warn('Lock contention on multistore file, opening '
+                            'in read-only mode.')
+            elif e.errno == errno.EACCES:
+                logger.warn('Cannot access credentials file.')
+            else:
+                raise
+        if not self._file.is_locked():
+            self._read_only = True
+            if self._warn_on_readonly:
+                logger.warn('The credentials file (%s) is not writable. '
+                            'Opening in read-only mode. Any refreshed '
+                            'credentials will only be '
+                            'valid for this run.', self._file.filename())
+
+        if os.path.getsize(self._file.filename()) == 0:
+            logger.debug('Initializing empty multistore file')
+            # The multistore is empty so write out an empty file.
+            self._data = {}
+            self._write()
+        elif not self._read_only or self._data is None:
+            # Only refresh the data if we are read/write or we haven't
+            # cached the data yet. If we are readonly, we assume is isn't
+            # changing out from under us and that we only have to read it
+            # once. This prevents us from whacking any new access keys that
+            # we have cached in memory but were unable to write out.
+            self._refresh_data_cache()
+
+    def _unlock(self):
+        """Release the lock on the multistore."""
+        self._file.unlock_and_close()
+        self._thread_lock.release()
+
+    def _locked_json_read(self):
+        """Get the raw content of the multistore file.
+
+        The multistore must be locked when this is called.
+
+        Returns:
+            The contents of the multistore decoded as JSON.
+        """
+        assert self._thread_lock.locked()
+        self._file.file_handle().seek(0)
+        return json.load(self._file.file_handle())
+
+    def _locked_json_write(self, data):
+        """Write a JSON serializable data structure to the multistore.
+
+        The multistore must be locked when this is called.
+
+        Args:
+            data: The data to be serialized and written.
+        """
+        assert self._thread_lock.locked()
+        if self._read_only:
+            return
+        self._file.file_handle().seek(0)
+        json.dump(data, self._file.file_handle(),
+                  sort_keys=True, indent=2, separators=(',', ': '))
+        self._file.file_handle().truncate()
+
+    def _refresh_data_cache(self):
+        """Refresh the contents of the multistore.
+
+        The multistore must be locked when this is called.
+
+        Raises:
+            NewerCredentialStoreError: Raised when a newer client has written
+            the store.
+        """
+        self._data = {}
+        try:
+            raw_data = self._locked_json_read()
+        except Exception:
+            logger.warn('Credential data store could not be loaded. '
+                        'Will ignore and overwrite.')
+            return
+
+        version = 0
+        try:
+            version = raw_data['file_version']
+        except Exception:
+            logger.warn('Missing version for credential data store. It may be '
+                        'corrupt or an old version. Overwriting.')
+        if version > 1:
+            raise NewerCredentialStoreError(
+                'Credential file has file_version of {0}. '
+                'Only file_version of 1 is supported.'.format(version))
+
+        credentials = []
+        try:
+            credentials = raw_data['data']
+        except (TypeError, KeyError):
+            pass
+
+        for cred_entry in credentials:
+            try:
+                key, credential = self._decode_credential_from_json(cred_entry)
+                self._data[key] = credential
+            except:
+                # If something goes wrong loading a credential, just ignore it
+                logger.info('Error decoding credential, skipping',
+                            exc_info=True)
+
+    def _decode_credential_from_json(self, cred_entry):
+        """Load a credential from our JSON serialization.
+
+        Args:
+            cred_entry: A dict entry from the data member of our format
+
+        Returns:
+            (key, cred) where the key is the key tuple and the cred is the
+            OAuth2Credential object.
+        """
+        raw_key = cred_entry['key']
+        key = _dict_to_tuple_key(raw_key)
+        credential = None
+        credential = client.Credentials.new_from_json(
+            json.dumps(cred_entry['credential']))
+        return (key, credential)
+
+    def _write(self):
+        """Write the cached data back out.
+
+        The multistore must be locked.
+        """
+        raw_data = {'file_version': 1}
+        raw_creds = []
+        raw_data['data'] = raw_creds
+        for (cred_key, cred) in self._data.items():
+            raw_key = dict(cred_key)
+            raw_cred = json.loads(cred.to_json())
+            raw_creds.append({'key': raw_key, 'credential': raw_cred})
+        self._locked_json_write(raw_data)
+
+    def _get_all_credential_keys(self):
+        """Gets all the registered credential keys in the multistore.
+
+        Returns:
+            A list of dictionaries corresponding to all the keys currently
+            registered
+        """
+        return [dict(key) for key in self._data.keys()]
+
+    def _get_credential(self, key):
+        """Get a credential from the multistore.
+
+        The multistore must be locked.
+
+        Args:
+            key: The key used to retrieve the credential
+
+        Returns:
+            The credential specified or None if not present
+        """
+        return self._data.get(key, None)
+
+    def _update_credential(self, key, cred):
+        """Update a credential and write the multistore.
+
+        This must be called when the multistore is locked.
+
+        Args:
+            key: The key used to retrieve the credential
+            cred: The OAuth2Credential to update/set
+        """
+        self._data[key] = cred
+        self._write()
+
+    def _delete_credential(self, key):
+        """Delete a credential and write the multistore.
+
+        This must be called when the multistore is locked.
+
+        Args:
+            key: The key used to retrieve the credential
+        """
+        try:
+            del self._data[key]
+        except KeyError:
+            pass
+        self._write()
+
+    def _get_storage(self, key):
+        """Get a Storage object to get/set a credential.
+
+        This Storage is a 'view' into the multistore.
+
+        Args:
+            key: The key used to retrieve the credential
+
+        Returns:
+            A Storage object that can be used to get/set this cred
+        """
+        return self._Storage(self, key)
diff --git a/oauth2client/contrib/xsrfutil.py b/oauth2client/contrib/xsrfutil.py
index 7c3ec03..c03e679 100644
--- a/oauth2client/contrib/xsrfutil.py
+++ b/oauth2client/contrib/xsrfutil.py
@@ -20,7 +20,12 @@
 import time
 
 from oauth2client import _helpers
+from oauth2client import util
 
+__authors__ = [
+    '"Doug Coker" <dcoker@google.com>',
+    '"Joe Gregorio" <jcgregorio@google.com>',
+]
 
 # Delimiter character
 DELIMITER = b':'
@@ -29,7 +34,7 @@
 DEFAULT_TIMEOUT_SECS = 60 * 60
 
 
-@_helpers.positional(2)
+@util.positional(2)
 def generate_token(key, user_id, action_id='', when=None):
     """Generates a URL-safe token for the given user, action, time tuple.
 
@@ -57,7 +62,7 @@
     return token
 
 
-@_helpers.positional(3)
+@util.positional(3)
 def validate_token(key, token, user_id, action_id="", current_time=None):
     """Validates that the given token authorizes the user for the action.
 
diff --git a/oauth2client/file.py b/oauth2client/file.py
index 3551c80..feede11 100644
--- a/oauth2client/file.py
+++ b/oauth2client/file.py
@@ -21,10 +21,16 @@
 import os
 import threading
 
-from oauth2client import _helpers
 from oauth2client import client
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+class CredentialsFileSymbolicLinkError(Exception):
+    """Credentials files must not be symbolic links."""
+
+
 class Storage(client.Storage):
     """Store and retrieve a single credential to and from a file."""
 
@@ -32,6 +38,11 @@
         super(Storage, self).__init__(lock=threading.Lock())
         self._filename = filename
 
+    def _validate_file(self):
+        if os.path.islink(self._filename):
+            raise CredentialsFileSymbolicLinkError(
+                'File: {0} is a symbolic link.'.format(self._filename))
+
     def locked_get(self):
         """Retrieve Credential from file.
 
@@ -39,10 +50,10 @@
             oauth2client.client.Credentials
 
         Raises:
-            IOError if the file is a symbolic link.
+            CredentialsFileSymbolicLinkError if the file is a symbolic link.
         """
         credentials = None
-        _helpers.validate_file(self._filename)
+        self._validate_file()
         try:
             f = open(self._filename, 'rb')
             content = f.read()
@@ -78,10 +89,10 @@
             credentials: Credentials, the credentials to store.
 
         Raises:
-            IOError if the file is a symbolic link.
+            CredentialsFileSymbolicLinkError if the file is a symbolic link.
         """
         self._create_file_if_needed()
-        _helpers.validate_file(self._filename)
+        self._validate_file()
         f = open(self._filename, 'w')
         f.write(credentials.to_json())
         f.close()
diff --git a/oauth2client/service_account.py b/oauth2client/service_account.py
index 540bfaa..bdcfd69 100644
--- a/oauth2client/service_account.py
+++ b/oauth2client/service_account.py
@@ -25,6 +25,7 @@
 from oauth2client import client
 from oauth2client import crypt
 from oauth2client import transport
+from oauth2client import util
 
 
 _PASSWORD_DEFAULT = 'notasecret'
@@ -109,7 +110,7 @@
 
         self._service_account_email = service_account_email
         self._signer = signer
-        self._scopes = _helpers.scopes_to_string(scopes)
+        self._scopes = util.scopes_to_string(scopes)
         self._private_key_id = private_key_id
         self.client_id = client_id
         self._user_agent = user_agent
@@ -649,22 +650,9 @@
         return result
 
     def refresh(self, http):
-        """Refreshes the access_token.
-
-        The HTTP object is unused since no request needs to be made to
-        get a new token, it can just be generated locally.
-
-        Args:
-            http: unused HTTP object
-        """
         self._refresh(None)
 
-    def _refresh(self, http):
-        """Refreshes the access_token.
-
-        Args:
-            http: unused HTTP object
-        """
+    def _refresh(self, http_request):
         self.access_token, self.token_expiry = self._create_token()
 
     def _create_token(self, additional_claims=None):
diff --git a/oauth2client/tools.py b/oauth2client/tools.py
index 5166993..8947157 100644
--- a/oauth2client/tools.py
+++ b/oauth2client/tools.py
@@ -30,10 +30,11 @@
 from six.moves import input
 from six.moves import urllib
 
-from oauth2client import _helpers
 from oauth2client import client
+from oauth2client import util
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 __all__ = ['argparser', 'run_flow', 'message_if_missing']
 
 _CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
@@ -92,7 +93,6 @@
         help='Set the logging level of detail.')
     return parser
 
-
 # argparser is an ArgumentParser that contains command-line options expected
 # by tools.run(). Pass it in as part of the 'parents' argument to your own
 # ArgumentParser.
@@ -123,22 +123,22 @@
         if an error occurred.
         """
         self.send_response(http_client.OK)
-        self.send_header('Content-type', 'text/html')
+        self.send_header("Content-type", "text/html")
         self.end_headers()
-        parts = urllib.parse.urlparse(self.path)
-        query = _helpers.parse_unique_urlencoded(parts.query)
+        query = self.path.split('?', 1)[-1]
+        query = dict(urllib.parse.parse_qsl(query))
         self.server.query_params = query
         self.wfile.write(
-            b'<html><head><title>Authentication Status</title></head>')
+            b"<html><head><title>Authentication Status</title></head>")
         self.wfile.write(
-            b'<body><p>The authentication flow has completed.</p>')
-        self.wfile.write(b'</body></html>')
+            b"<body><p>The authentication flow has completed.</p>")
+        self.wfile.write(b"</body></html>")
 
     def log_message(self, format, *args):
         """Do not log messages to stdout while running as cmd. line program."""
 
 
-@_helpers.positional(3)
+@util.positional(3)
 def run_flow(flow, storage, flags=None, http=None):
     """Core code for a command-line application.
 
diff --git a/oauth2client/transport.py b/oauth2client/transport.py
index 79a61f1..8dbc60d 100644
--- a/oauth2client/transport.py
+++ b/oauth2client/transport.py
@@ -18,7 +18,7 @@
 import six
 from six.moves import http_client
 
-from oauth2client import _helpers
+from oauth2client._helpers import _to_bytes
 
 
 _LOGGER = logging.getLogger(__name__)
@@ -58,19 +58,13 @@
     return _CACHED_HTTP
 
 
-def get_http_object(*args, **kwargs):
+def get_http_object():
     """Return a new HTTP object.
 
-    Args:
-        *args: tuple, The positional arguments to be passed when
-               contructing a new HTTP object.
-        **kwargs: dict, The keyword arguments to be passed when
-                  contructing a new HTTP object.
-
     Returns:
         httplib2.Http, an HTTP object.
     """
-    return httplib2.Http(*args, **kwargs)
+    return httplib2.Http()
 
 
 def _initialize_headers(headers):
@@ -127,7 +121,7 @@
                 k = str(k)
             if not isinstance(v, six.binary_type):
                 v = str(v)
-            clean[_helpers._to_bytes(k)] = _helpers._to_bytes(v)
+            clean[_to_bytes(k)] = _to_bytes(v)
     except UnicodeEncodeError:
         from oauth2client.client import NonAsciiHeaderError
         raise NonAsciiHeaderError(k, ': ', v)
@@ -170,9 +164,9 @@
                _STREAM_PROPERTIES):
             body_stream_position = body.tell()
 
-        resp, content = request(orig_request_method, uri, method, body,
-                                clean_headers(headers),
-                                redirections, connection_type)
+        resp, content = orig_request_method(uri, method, body,
+                                            clean_headers(headers),
+                                            redirections, connection_type)
 
         # A stored token may expire between the time it is retrieved and
         # the time the request is made, so we may need to try twice.
@@ -188,9 +182,9 @@
             if body_stream_position is not None:
                 body.seek(body_stream_position)
 
-            resp, content = request(orig_request_method, uri, method, body,
-                                    clean_headers(headers),
-                                    redirections, connection_type)
+            resp, content = orig_request_method(uri, method, body,
+                                                clean_headers(headers),
+                                                redirections, connection_type)
 
         return resp, content
 
@@ -198,7 +192,7 @@
     http.request = new_request
 
     # Set credentials as a property of the request method.
-    http.request.credentials = credentials
+    setattr(http.request, 'credentials', credentials)
 
 
 def wrap_http_for_jwt_access(credentials, http):
@@ -228,9 +222,9 @@
             if (credentials.access_token is None or
                     credentials.access_token_expired):
                 credentials.refresh(None)
-            return request(authenticated_request_method, uri,
-                           method, body, headers, redirections,
-                           connection_type)
+            return authenticated_request_method(uri, method, body,
+                                                headers, redirections,
+                                                connection_type)
         else:
             # If we don't have an 'aud' (audience) claim,
             # create a 1-time token with the uri root as the audience
@@ -240,46 +234,12 @@
             token, unused_expiry = credentials._create_token({'aud': uri_root})
 
             headers['Authorization'] = 'Bearer ' + token
-            return request(orig_request_method, uri, method, body,
-                           clean_headers(headers),
-                           redirections, connection_type)
+            return orig_request_method(uri, method, body,
+                                       clean_headers(headers),
+                                       redirections, connection_type)
 
     # Replace the request method with our own closure.
     http.request = new_request
 
-    # Set credentials as a property of the request method.
-    http.request.credentials = credentials
-
-
-def request(http, uri, method='GET', body=None, headers=None,
-            redirections=httplib2.DEFAULT_MAX_REDIRECTS,
-            connection_type=None):
-    """Make an HTTP request with an HTTP object and arguments.
-
-    Args:
-        http: httplib2.Http, an http object to be used to make requests.
-        uri: string, The URI to be requested.
-        method: string, The HTTP method to use for the request. Defaults
-                to 'GET'.
-        body: string, The payload / body in HTTP request. By default
-              there is no payload.
-        headers: dict, Key-value pairs of request headers. By default
-                 there are no headers.
-        redirections: int, The number of allowed 203 redirects for
-                      the request. Defaults to 5.
-        connection_type: httplib.HTTPConnection, a subclass to be used for
-                         establishing connection. If not set, the type
-                         will be determined from the ``uri``.
-
-    Returns:
-        tuple, a pair of a httplib2.Response with the status code and other
-        headers and the bytes of the content returned.
-    """
-    # NOTE: Allowing http or http.request is temporary (See Issue 601).
-    http_callable = getattr(http, 'request', http)
-    return http_callable(uri, method=method, body=body, headers=headers,
-                         redirections=redirections,
-                         connection_type=connection_type)
-
 
 _CACHED_HTTP = httplib2.Http(MemoryCache())
diff --git a/oauth2client/util.py b/oauth2client/util.py
new file mode 100644
index 0000000..e3ba62b
--- /dev/null
+++ b/oauth2client/util.py
@@ -0,0 +1,206 @@
+# Copyright 2014 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.
+
+"""Common utility library."""
+
+import functools
+import inspect
+import logging
+
+import six
+from six.moves import urllib
+
+
+__author__ = [
+    'rafek@google.com (Rafe Kaplan)',
+    'guido@google.com (Guido van Rossum)',
+]
+
+__all__ = [
+    'positional',
+    'POSITIONAL_WARNING',
+    'POSITIONAL_EXCEPTION',
+    'POSITIONAL_IGNORE',
+]
+
+logger = logging.getLogger(__name__)
+
+POSITIONAL_WARNING = 'WARNING'
+POSITIONAL_EXCEPTION = 'EXCEPTION'
+POSITIONAL_IGNORE = 'IGNORE'
+POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
+                            POSITIONAL_IGNORE])
+
+positional_parameters_enforcement = POSITIONAL_WARNING
+
+
+def positional(max_positional_args):
+    """A decorator to declare that only the first N arguments my be positional.
+
+    This decorator makes it easy to support Python 3 style keyword-only
+    parameters. For example, in Python 3 it is possible to write::
+
+        def fn(pos1, *, kwonly1=None, kwonly1=None):
+            ...
+
+    All named parameters after ``*`` must be a keyword::
+
+        fn(10, 'kw1', 'kw2')  # Raises exception.
+        fn(10, kwonly1='kw1')  # Ok.
+
+    Example
+    ^^^^^^^
+
+    To define a function like above, do::
+
+        @positional(1)
+        def fn(pos1, kwonly1=None, kwonly2=None):
+            ...
+
+    If no default value is provided to a keyword argument, it becomes a
+    required keyword argument::
+
+        @positional(0)
+        def fn(required_kw):
+            ...
+
+    This must be called with the keyword parameter::
+
+        fn()  # Raises exception.
+        fn(10)  # Raises exception.
+        fn(required_kw=10)  # Ok.
+
+    When defining instance or class methods always remember to account for
+    ``self`` and ``cls``::
+
+        class MyClass(object):
+
+            @positional(2)
+            def my_method(self, pos1, kwonly1=None):
+                ...
+
+            @classmethod
+            @positional(2)
+            def my_method(cls, pos1, kwonly1=None):
+                ...
+
+    The positional decorator behavior is controlled by
+    ``util.positional_parameters_enforcement``, which may be set to
+    ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
+    ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
+    nothing, respectively, if a declaration is violated.
+
+    Args:
+        max_positional_arguments: Maximum number of positional arguments. All
+                                  parameters after the this index must be
+                                  keyword only.
+
+    Returns:
+        A decorator that prevents using arguments after max_positional_args
+        from being used as positional parameters.
+
+    Raises:
+        TypeError: if a key-word only argument is provided as a positional
+                   parameter, but only if
+                   util.positional_parameters_enforcement is set to
+                   POSITIONAL_EXCEPTION.
+    """
+
+    def positional_decorator(wrapped):
+        @functools.wraps(wrapped)
+        def positional_wrapper(*args, **kwargs):
+            if len(args) > max_positional_args:
+                plural_s = ''
+                if max_positional_args != 1:
+                    plural_s = 's'
+                message = ('{function}() takes at most {args_max} positional '
+                           'argument{plural} ({args_given} given)'.format(
+                               function=wrapped.__name__,
+                               args_max=max_positional_args,
+                               args_given=len(args),
+                               plural=plural_s))
+                if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
+                    raise TypeError(message)
+                elif positional_parameters_enforcement == POSITIONAL_WARNING:
+                    logger.warning(message)
+            return wrapped(*args, **kwargs)
+        return positional_wrapper
+
+    if isinstance(max_positional_args, six.integer_types):
+        return positional_decorator
+    else:
+        args, _, _, defaults = inspect.getargspec(max_positional_args)
+        return positional(len(args) - len(defaults))(max_positional_args)
+
+
+def scopes_to_string(scopes):
+    """Converts scope value to a string.
+
+    If scopes is a string then it is simply passed through. If scopes is an
+    iterable then a string is returned that is all the individual scopes
+    concatenated with spaces.
+
+    Args:
+        scopes: string or iterable of strings, the scopes.
+
+    Returns:
+        The scopes formatted as a single string.
+    """
+    if isinstance(scopes, six.string_types):
+        return scopes
+    else:
+        return ' '.join(scopes)
+
+
+def string_to_scopes(scopes):
+    """Converts stringifed scope value to a list.
+
+    If scopes is a list then it is simply passed through. If scopes is an
+    string then a list of each individual scope is returned.
+
+    Args:
+        scopes: a string or iterable of strings, the scopes.
+
+    Returns:
+        The scopes in a list.
+    """
+    if not scopes:
+        return []
+    if isinstance(scopes, six.string_types):
+        return scopes.split(' ')
+    else:
+        return scopes
+
+
+def _add_query_parameter(url, name, value):
+    """Adds a query parameter to a url.
+
+    Replaces the current value if it already exists in the URL.
+
+    Args:
+        url: string, url to add the query parameter to.
+        name: string, query parameter name.
+        value: string, query parameter value.
+
+    Returns:
+        Updated query parameter. Does not update the url if value is None.
+    """
+    if value is None:
+        return url
+    else:
+        parsed = list(urllib.parse.urlparse(url))
+        q = dict(urllib.parse.parse_qsl(parsed[4]))
+        q[name] = value
+        parsed[4] = urllib.parse.urlencode(q)
+        return urllib.parse.urlunparse(parsed)
diff --git a/samples/django/README.md b/samples/django/README.md
deleted file mode 100644
index 27c0fda..0000000
--- a/samples/django/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Django Samples
-
-These two sample Django apps provide a skeleton for the two main use cases of the
-`oauth2client.contrib.django_util` helpers.
-
-Please see the 
-[core docs](https://oauth2client.readthedocs.io/en/latest/) for more information and usage examples.
-
-## google_user
-
-This is the simpler use case of the library. It assumes you are using Google OAuth as your primary
-authorization and authentication mechanism for your application. Users log in with their Google ID 
-and their OAuth2 credentials are stored inside the session. 
- 
-## django_user
- 
-This is the use case where the application is already using the Django authorization system and
-has a Django model with a `django.contrib.auth.models.User` field, and would like to attach
-a Google OAuth2 credentials object to that model. Users have to login, and then can login with 
-their Google account to associate the Google account with the user in the Django system.
-Credentials will be stored in the Django ORM backend.
diff --git a/samples/django/django_user/manage.py b/samples/django/django_user/manage.py
deleted file mode 100755
index 1a30708..0000000
--- a/samples/django/django_user/manage.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2016 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.
-import os
-import sys
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)
diff --git a/samples/django/django_user/myoauth/__init__.py b/samples/django/django_user/myoauth/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/samples/django/django_user/myoauth/__init__.py
+++ /dev/null
diff --git a/samples/django/django_user/myoauth/settings.py b/samples/django/django_user/myoauth/settings.py
deleted file mode 100644
index 5ef2f99..0000000
--- a/samples/django/django_user/myoauth/settings.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright 2016 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.
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
-
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'eiw+mvmua#98n@p2xq+c#liz@r2&#-s07nkgz)+$zcl^o4$-$o'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = []
-
-# Application definition
-
-INSTALLED_APPS = (
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'polls',
-    'oauth2client.contrib.django_util',
-)
-
-MIDDLEWARE_CLASSES = (
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'django.middleware.security.SecurityMiddleware',
-)
-
-ROOT_URLCONF = 'myoauth.urls'
-
-TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-            ],
-        },
-    },
-]
-
-WSGI_APPLICATION = 'myoauth.wsgi.application'
-
-# Database
-# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
-    }
-}
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.8/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.8/howto/static-files/
-
-STATIC_URL = '/static/'
-
-GOOGLE_OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID'
-
-GOOGLE_OAUTH2_CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
-
-GOOGLE_OAUTH2_SCOPES = (
-    'email', 'profile')
-
-GOOGLE_OAUTH2_STORAGE_MODEL = {
-    'model': 'polls.models.CredentialsModel',
-    'user_property': 'user_id',
-    'credentials_property': 'credential',
-}
-
-LOGIN_URL = '/login'
diff --git a/samples/django/django_user/myoauth/urls.py b/samples/django/django_user/myoauth/urls.py
deleted file mode 100644
index 6636b4e..0000000
--- a/samples/django/django_user/myoauth/urls.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2016 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.
-
-from django.conf import urls
-from django.contrib import admin
-import django.contrib.auth.views
-from polls import views
-
-import oauth2client.contrib.django_util.site as django_util_site
-
-
-urlpatterns = [
-    urls.url(r'^$', views.index),
-    urls.url(r'^profile_required$', views.get_profile_required),
-    urls.url(r'^profile_enabled$', views.get_profile_optional),
-    urls.url(r'^admin/', urls.include(admin.site.urls)),
-    urls.url(r'^login', django.contrib.auth.views.login, name="login"),
-    urls.url(r'^oauth2/', urls.include(django_util_site.urls)),
-]
diff --git a/samples/django/django_user/myoauth/wsgi.py b/samples/django/django_user/myoauth/wsgi.py
deleted file mode 100644
index 39ee648..0000000
--- a/samples/django/django_user/myoauth/wsgi.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2016 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.
-
-import os
-
-from django.core.wsgi import get_wsgi_application
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings")
-
-application = get_wsgi_application()
diff --git a/samples/django/django_user/polls/__init__.py b/samples/django/django_user/polls/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/samples/django/django_user/polls/__init__.py
+++ /dev/null
diff --git a/samples/django/django_user/polls/models.py b/samples/django/django_user/polls/models.py
deleted file mode 100644
index 563f66e..0000000
--- a/samples/django/django_user/polls/models.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2016 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.
-
-from django.contrib.auth.models import User
-from django.db import models
-
-from oauth2client.contrib.django_util.models import CredentialsField
-
-
-class CredentialsModel(models.Model):
-    user_id = models.OneToOneField(User)
-    credential = CredentialsField()
diff --git a/samples/django/django_user/polls/templates/registration/login.html b/samples/django/django_user/polls/templates/registration/login.html
deleted file mode 100644
index c43450e..0000000
--- a/samples/django/django_user/polls/templates/registration/login.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!--
-# Copyright 2016 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
--->
-
-{% if form.errors %}
-<p>Your username and password didn't match. Please try again.</p>
-{% endif %}
-
-{% if next %}
-{% if user.is_authenticated %}
-<p>Your account doesn't have access to this page. To proceed,
-    please login with an account that has access.</p>
-{% else %}
-<p>Please login to see this page.</p>
-{% endif %}
-{% endif %}
-
-<form method="post" action="{% url 'login' %}">
-    {% csrf_token %}
-    <table>
-        <tr>
-            <td>{{ form.username.label_tag }}</td>
-            <td>{{ form.username }}</td>
-        </tr>
-        <tr>
-            <td>{{ form.password.label_tag }}</td>
-            <td>{{ form.password }}</td>
-        </tr>
-    </table>
-
-    <input type="submit" value="login" />
-    <input type="hidden" name="next" value="{{ next }}" />
-</form>
diff --git a/samples/django/django_user/polls/views.py b/samples/django/django_user/polls/views.py
deleted file mode 100644
index 5888330..0000000
--- a/samples/django/django_user/polls/views.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2016 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.
-
-from django.http import HttpResponse
-
-from oauth2client.contrib.django_util import decorators
-
-
-def index(request):
-    return HttpResponse("Hello world!")
-
-
-@decorators.oauth_required
-def get_profile_required(request):
-    resp, content = request.oauth.http.request(
-        'https://www.googleapis.com/plus/v1/people/me')
-    return HttpResponse(content)
-
-
-@decorators.oauth_enabled
-def get_profile_optional(request):
-    if request.oauth.has_credentials():
-        # this could be passed into a view
-        # request.oauth.http is also initialized
-        return HttpResponse('User email: {}'.format(
-            request.oauth.credentials.id_token['email']))
-    else:
-        return HttpResponse(
-            'Here is an OAuth Authorize link:<a href="{}">Authorize</a>'
-            .format(request.oauth.get_authorize_redirect()))
diff --git a/samples/django/django_user/requirements.txt b/samples/django/django_user/requirements.txt
deleted file mode 100644
index b42af1f..0000000
--- a/samples/django/django_user/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Django==1.10.0
-oauth2client==3.0.0
-jsonpickle==0.9.3
diff --git a/samples/django/google_user/manage.py b/samples/django/google_user/manage.py
deleted file mode 100755
index 1a30708..0000000
--- a/samples/django/google_user/manage.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2016 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.
-import os
-import sys
-
-if __name__ == "__main__":
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings")
-
-    from django.core.management import execute_from_command_line
-
-    execute_from_command_line(sys.argv)
diff --git a/samples/django/google_user/myoauth/__init__.py b/samples/django/google_user/myoauth/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/samples/django/google_user/myoauth/__init__.py
+++ /dev/null
diff --git a/samples/django/google_user/myoauth/settings.py b/samples/django/google_user/myoauth/settings.py
deleted file mode 100644
index e08661d..0000000
--- a/samples/django/google_user/myoauth/settings.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright 2016 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.
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import os
-
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'eiw+mvmua#98n@p2xq+c#liz@r2&#-s07nkgz)+$zcl^o4$-$o'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = []
-
-# Application definition
-
-INSTALLED_APPS = (
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'polls',
-    'oauth2client.contrib.django_util',
-)
-
-MIDDLEWARE_CLASSES = (
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'django.middleware.security.SecurityMiddleware',
-)
-
-ROOT_URLCONF = 'myoauth.urls'
-
-TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-            ],
-        },
-    },
-]
-
-WSGI_APPLICATION = 'myoauth.wsgi.application'
-
-# Database
-# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
-    }
-}
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.8/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.8/howto/static-files/
-
-STATIC_URL = '/static/'
-
-GOOGLE_OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID'
-
-GOOGLE_OAUTH2_CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
-
-GOOGLE_OAUTH2_SCOPES = (
-    'email', 'profile')
diff --git a/samples/django/google_user/myoauth/urls.py b/samples/django/google_user/myoauth/urls.py
deleted file mode 100644
index 4d3d0a1..0000000
--- a/samples/django/google_user/myoauth/urls.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2016 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.
-
-from django.conf import urls
-from polls import views
-
-import oauth2client.contrib.django_util.site as django_util_site
-
-
-urlpatterns = [
-    urls.url(r'^$', views.index),
-    urls.url(r'^profile_required$', views.get_profile_required),
-    urls.url(r'^profile_enabled$', views.get_profile_optional),
-    urls.url(r'^oauth2/', urls.include(django_util_site.urls))
-]
diff --git a/samples/django/google_user/myoauth/wsgi.py b/samples/django/google_user/myoauth/wsgi.py
deleted file mode 100644
index 39ee648..0000000
--- a/samples/django/google_user/myoauth/wsgi.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2016 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.
-
-import os
-
-from django.core.wsgi import get_wsgi_application
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myoauth.settings")
-
-application = get_wsgi_application()
diff --git a/samples/django/google_user/polls/__init__.py b/samples/django/google_user/polls/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/samples/django/google_user/polls/__init__.py
+++ /dev/null
diff --git a/samples/django/google_user/polls/views.py b/samples/django/google_user/polls/views.py
deleted file mode 100644
index e4b9119..0000000
--- a/samples/django/google_user/polls/views.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2016 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.
-
-from django import http
-
-from oauth2client.contrib.django_util import decorators
-
-
-def index(request):
-    return http.HttpResponse("Hello world!")
-
-
-@decorators.oauth_required
-def get_profile_required(request):
-    resp, content = request.oauth.http.request(
-        'https://www.googleapis.com/plus/v1/people/me')
-    return http.HttpResponse(content)
-
-
-@decorators.oauth_enabled
-def get_profile_optional(request):
-    if request.oauth.has_credentials():
-        # this could be passed into a view
-        # request.oauth.http is also initialized
-        return http.HttpResponse('User email: {}'.format(
-            request.oauth.credentials.id_token['email']))
-    else:
-        return http.HttpResponse(
-            'Here is an OAuth Authorize link:<a href="{}">Authorize</a>'
-            .format(request.oauth.get_authorize_redirect()))
diff --git a/samples/django/google_user/requirements.txt b/samples/django/google_user/requirements.txt
deleted file mode 100644
index b42af1f..0000000
--- a/samples/django/google_user/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Django==1.10.0
-oauth2client==3.0.0
-jsonpickle==0.9.3
diff --git a/scripts/fetch_gae_sdk.py b/scripts/fetch_gae_sdk.py
new file mode 100755
index 0000000..24a6db5
--- /dev/null
+++ b/scripts/fetch_gae_sdk.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+"""Fetch the most recent GAE SDK and decompress it in the current directory.
+
+Usage:
+    fetch_gae_sdk.py [<dest_dir>]
+
+Current releases are listed here:
+    https://www.googleapis.com/storage/v1/b/appengine-sdks/o?prefix=featured
+"""
+from __future__ import print_function
+
+import json
+import os
+import StringIO
+import sys
+import urllib2
+import zipfile
+
+
+_SDK_URL = (
+    'https://www.googleapis.com/storage/v1/b/appengine-sdks/o?prefix=featured')
+
+
+def get_gae_versions():
+    try:
+        version_info_json = urllib2.urlopen(_SDK_URL).read()
+    except:
+        return {}
+    try:
+        version_info = json.loads(version_info_json)
+    except:
+        return {}
+    return version_info.get('items', {})
+
+
+def _version_tuple(v):
+    version_string = os.path.splitext(v['name'])[0].rpartition('_')[2]
+    return tuple(int(x) for x in version_string.split('.'))
+
+
+def get_sdk_urls(sdk_versions):
+    python_releases = [v for v in sdk_versions
+                       if v['name'].startswith('featured/google_appengine')]
+    current_releases = sorted(python_releases, key=_version_tuple,
+                              reverse=True)
+    return [release['mediaLink'] for release in current_releases]
+
+
+def main(argv):
+    if len(argv) > 2:
+        print('Usage: {0} [<destination_dir>]'.format(argv[0]))
+        return 1
+    dest_dir = argv[1] if len(argv) > 1 else '.'
+    if not os.path.exists(dest_dir):
+        os.makedirs(dest_dir)
+
+    if os.path.exists(os.path.join(dest_dir, 'google_appengine')):
+        print('GAE SDK already installed at {0}, exiting.'.format(dest_dir))
+        return 0
+
+    sdk_versions = get_gae_versions()
+    if not sdk_versions:
+        print('Error fetching GAE SDK version info')
+        return 1
+    sdk_urls = get_sdk_urls(sdk_versions)
+    for sdk_url in sdk_urls:
+        try:
+            sdk_contents = StringIO.StringIO(urllib2.urlopen(sdk_url).read())
+            break
+        except:
+            pass
+    else:
+        print('Could not read SDK from any of ', sdk_urls)
+        return 1
+    sdk_contents.seek(0)
+    try:
+        zip_contents = zipfile.ZipFile(sdk_contents)
+        zip_contents.extractall(dest_dir)
+    except:
+        print('Error extracting SDK contents')
+        return 1
+
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv[:]))
diff --git a/scripts/install.sh b/scripts/install.sh
index e1ed5c5..0ef7ad2 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -16,18 +16,16 @@
 
 set -ev
 
-pip install --upgrade pip setuptools tox coveralls
-
-# App Engine tests require the App Engine SDK.
-if [[ "${TOX_ENV}" == "gae" || "${TOX_ENV}" == "cover" ]]; then
-    pip install gcp-devrel-py-tools
-    gcp-devrel-py-tools download-appengine-sdk `dirname ${GAE_PYTHONPATH}`
+pip install tox
+if [[ "${TOX_ENV}" == "pypy" ]]; then
+    git clone https://github.com/yyuu/pyenv.git ${HOME}/.pyenv
+    PYENV_ROOT="${HOME}/.pyenv"
+    PATH="${PYENV_ROOT}/bin:${PATH}"
+    eval "$(pyenv init -)"
+    pyenv install pypy-2.6.0
+    pyenv global pypy-2.6.0
 fi
 
-# Travis ships with an old version of PyPy, so install at least version 2.6.
-if [[ "${TOX_ENV}" == "pypy" ]]; then
-    if [ ! -d "${HOME}/.pyenv/bin" ]; then
-        git clone https://github.com/yyuu/pyenv.git ${HOME}/.pyenv
-    fi
-    ${HOME}/.pyenv/bin/pyenv install --skip-existing pypy-2.6.0
+if [[ "${TOX_ENV}" == "gae" && ! -d ${GAE_PYTHONPATH} ]]; then
+    python scripts/fetch_gae_sdk.py `dirname ${GAE_PYTHONPATH}`
 fi
diff --git a/scripts/run.sh b/scripts/run.sh
index c774f24..0b537e2 100755
--- a/scripts/run.sh
+++ b/scripts/run.sh
@@ -16,11 +16,10 @@
 
 set -ev
 
-# If in the pypy environment, activate the never version of pypy provided by
-# pyenv.
 if [[ "${TOX_ENV}" == "pypy" ]]; then
-    PATH="${HOME}/.pyenv/versions/pypy-2.6.0/bin:${PATH}"
-    export PATH
+    PYENV_ROOT="${HOME}/.pyenv"
+    PATH="${PYENV_ROOT}/bin:${PATH}"
+    eval "$(pyenv init -)"
+    pyenv global pypy-2.6.0
 fi
-
 tox -e ${TOX_ENV}
diff --git a/scripts/run_gce_system_tests.py b/scripts/run_gce_system_tests.py
index 80794bd..d446f9c 100644
--- a/scripts/run_gce_system_tests.py
+++ b/scripts/run_gce_system_tests.py
@@ -13,26 +13,26 @@
 # limitations under the License.
 
 import json
-import unittest
 
+import httplib2
 from six.moves import http_client
 from six.moves import urllib
+import unittest2
 
-import oauth2client
-from oauth2client import client
-from oauth2client import transport
-from oauth2client.contrib import gce
+from oauth2client import GOOGLE_TOKEN_INFO_URI
+from oauth2client.client import GoogleCredentials
+from oauth2client.contrib.gce import AppAssertionCredentials
 
 
-class TestComputeEngine(unittest.TestCase):
+class TestComputeEngine(unittest2.TestCase):
 
     def test_application_default(self):
-        default_creds = client.GoogleCredentials.get_application_default()
-        self.assertIsInstance(default_creds, gce.AppAssertionCredentials)
+        default_creds = GoogleCredentials.get_application_default()
+        self.assertIsInstance(default_creds, AppAssertionCredentials)
 
     def test_token_info(self):
-        credentials = gce.AppAssertionCredentials([])
-        http = transport.get_http_object()
+        credentials = AppAssertionCredentials([])
+        http = httplib2.Http()
 
         # First refresh to get the access token.
         self.assertIsNone(credentials.access_token)
@@ -41,9 +41,9 @@
 
         # Then check the access token against the token info API.
         query_params = {'access_token': credentials.access_token}
-        token_uri = (oauth2client.GOOGLE_TOKEN_INFO_URI + '?' +
+        token_uri = (GOOGLE_TOKEN_INFO_URI + '?' +
                      urllib.parse.urlencode(query_params))
-        response, content = transport.request(http, token_uri)
+        response, content = http.request(token_uri)
         self.assertEqual(response.status, http_client.OK)
 
         content = content.decode('utf-8')
@@ -53,4 +53,4 @@
 
 
 if __name__ == '__main__':
-    unittest.main()
+    unittest2.main()
diff --git a/scripts/run_system_tests.py b/scripts/run_system_tests.py
index 4c9c80c..ce99e7c 100644
--- a/scripts/run_system_tests.py
+++ b/scripts/run_system_tests.py
@@ -15,12 +15,12 @@
 import json
 import os
 
+import httplib2
 from six.moves import http_client
 
 import oauth2client
 from oauth2client import client
-from oauth2client import service_account
-from oauth2client import transport
+from oauth2client.service_account import ServiceAccountCredentials
 
 
 JSON_KEY_PATH = os.getenv('OAUTH2CLIENT_TEST_JSON_KEY_PATH')
@@ -56,8 +56,8 @@
 
 
 def _check_user_info(credentials, expected_email):
-    http = credentials.authorize(transport.get_http_object())
-    response, content = transport.request(http, USER_INFO)
+    http = credentials.authorize(httplib2.Http())
+    response, content = http.request(USER_INFO)
     if response.status != http_client.OK:
         raise ValueError('Expected 200 OK response.')
 
@@ -68,14 +68,14 @@
 
 
 def run_json():
-    factory = service_account.ServiceAccountCredentials.from_json_keyfile_name
-    credentials = factory(JSON_KEY_PATH, scopes=SCOPE)
+    credentials = ServiceAccountCredentials.from_json_keyfile_name(
+        JSON_KEY_PATH, scopes=SCOPE)
     service_account_email = credentials._service_account_email
     _check_user_info(credentials, service_account_email)
 
 
 def run_p12():
-    credentials = service_account.ServiceAccountCredentials.from_p12_keyfile(
+    credentials = ServiceAccountCredentials.from_p12_keyfile(
         P12_KEY_EMAIL, P12_KEY_PATH, scopes=SCOPE)
     _check_user_info(credentials, P12_KEY_EMAIL)
 
diff --git a/scripts/run_system_tests.sh b/scripts/run_system_tests.sh
index 2e10e5c..7169eb7 100755
--- a/scripts/run_system_tests.sh
+++ b/scripts/run_system_tests.sh
@@ -19,9 +19,10 @@
 
 # If we're on Travis, we need to set up the environment.
 if [[ "${TRAVIS}" == "true" ]]; then
-  # If secure variables are available, run system test.
-  if [[ "${TRAVIS_SECURE_ENV_VARS}" ]]; then
-    echo "Running in Travis, decrypting stored key file."
+  # If merging to master and not a pull request, run system test.
+  if [[ "${TRAVIS_BRANCH}" == "master" ]] && \
+         [[ "${TRAVIS_PULL_REQUEST}" == "false" ]]; then
+    echo "Running in Travis during merge, decrypting stored key file."
     # Convert encrypted JSON key file into decrypted file to be used.
     openssl aes-256-cbc -K ${OAUTH2CLIENT_KEY} \
         -iv ${OAUTH2CLIENT_IV} \
@@ -33,8 +34,8 @@
         -in tests/data/key.p12.enc \
         -out ${OAUTH2CLIENT_TEST_P12_KEY_PATH} -d
     # Convert encrypted User JSON key file into decrypted file to be used.
-    openssl aes-256-cbc -K ${encrypted_1ee98544e5ca_key} \
-        -iv ${encrypted_1ee98544e5ca_iv} \
+    openssl aes-256-cbc -K ${OAUTH2CLIENT_KEY} \
+        -iv ${OAUTH2CLIENT_IV} \
         -in tests/data/user-key.json.enc \
         -out ${OAUTH2CLIENT_TEST_USER_KEY_PATH} -d
   else
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 2a9acf1..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[bdist_wheel]
-universal = 1
diff --git a/setup.py b/setup.py
index 47d86b7..686d1db 100644
--- a/setup.py
+++ b/setup.py
@@ -26,11 +26,11 @@
 
 import oauth2client
 
-if sys.version_info < (2, 7):
-    print('oauth2client requires python2 version >= 2.7.', file=sys.stderr)
+if sys.version_info < (2, 6):
+    print('oauth2client requires python2 version >= 2.6.', file=sys.stderr)
     sys.exit(1)
-if (3, 1) <= sys.version_info < (3, 4):
-    print('oauth2client requires python3 version >= 3.4.', file=sys.stderr)
+if (3, 1) <= sys.version_info < (3, 3):
+    print('oauth2client requires python3 version >= 3.3.', file=sys.stderr)
     sys.exit(1)
 
 install_requires = [
@@ -41,36 +41,29 @@
     'six>=1.6.1',
 ]
 
-long_desc = """
-oauth2client is a client library for OAuth 2.0.
-
-Note: oauth2client is now deprecated. No more features will be added to the
-    libraries and the core team is turning down support. We recommend you use
-    `google-auth <https://google-auth.readthedocs.io>`__ and
-    `oauthlib <http://oauthlib.readthedocs.io/>`__.
-"""
+long_desc = """The oauth2client is a client library for OAuth 2.0."""
 
 version = oauth2client.__version__
 
 setup(
-    name='oauth2client',
+    name="oauth2client",
     version=version,
-    description='OAuth 2.0 client library',
+    description="OAuth 2.0 client library",
     long_description=long_desc,
-    author='Google Inc.',
-    author_email='jonwayne+oauth2client@google.com',
-    url='http://github.com/google/oauth2client/',
+    author="Google Inc.",
+    url="http://github.com/google/oauth2client/",
     install_requires=install_requires,
-    packages=find_packages(exclude=('tests*',)),
-    license='Apache 2.0',
-    keywords='google oauth 2.0 http client',
+    packages=find_packages(),
+    license="Apache 2.0",
+    keywords="google oauth 2.0 http client",
     classifiers=[
         'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.3',
         'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
-        'Development Status :: 7 - Inactive',
+        'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: Apache Software License',
         'Operating System :: POSIX',
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29..5f6567c 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1,22 @@
+# 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.
+
+"""Test package set-up."""
+
+from oauth2client import util
+
+__author__ = 'afshar@google.com (Ali Afshar)'
+
+
+def setup_package():
+    """Run on testing package."""
+    util.positional_parameters_enforcement = util.POSITIONAL_EXCEPTION
diff --git a/tests/conftest.py b/tests/conftest.py
deleted file mode 100644
index caadb80..0000000
--- a/tests/conftest.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2016 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.
-
-"""Py.test hooks."""
-
-from oauth2client import _helpers
-
-
-def pytest_addoption(parser):
-    """Adds the --gae-sdk option to py.test.
-
-    This is used to enable the GAE tests. This has to be in this conftest.py
-    due to the way py.test collects conftest files."""
-    parser.addoption('--gae-sdk')
-
-
-def pytest_configure(config):
-    """Py.test hook called before loading tests."""
-    # Default of POSITIONAL_WARNING is too verbose for testing
-    _helpers.positional_parameters_enforcement = _helpers.POSITIONAL_EXCEPTION
diff --git a/tests/contrib/appengine/__init__.py b/tests/contrib/appengine/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/contrib/appengine/__init__.py
+++ /dev/null
diff --git a/tests/contrib/appengine/conftest.py b/tests/contrib/appengine/conftest.py
deleted file mode 100644
index b56fbcd..0000000
--- a/tests/contrib/appengine/conftest.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2016 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.
-
-"""App Engine py.test configuration."""
-
-import sys
-
-from six.moves import reload_module
-
-
-def set_up_gae_environment(sdk_path):
-    """Set up appengine SDK third-party imports.
-
-    The App Engine SDK does terrible things to the global interpreter state.
-    Because of this, this stuff can't be neatly undone. As such, it can't be
-    a fixture.
-    """
-    if 'google' in sys.modules:
-        # Some packages, such as protobuf, clobber the google
-        # namespace package. This prevents that.
-        reload_module(sys.modules['google'])
-
-    # This sets up google-provided libraries.
-    sys.path.insert(0, sdk_path)
-    import dev_appserver
-    dev_appserver.fix_sys_path()
-
-    # Fixes timezone and other os-level items.
-    import google.appengine.tools.os_compat  # noqa: unused import
-
-
-def pytest_configure(config):
-    """Configures the App Engine SDK imports on py.test startup."""
-    if config.getoption('gae_sdk') is not None:
-        set_up_gae_environment(config.getoption('gae_sdk'))
-
-
-def pytest_ignore_collect(path, config):
-    """Skip App Engine tests when --gae-sdk is not specified."""
-    return (
-        'contrib/appengine' in str(path) and
-        config.getoption('gae_sdk') is None)
diff --git a/tests/contrib/django_util/test_decorators.py b/tests/contrib/django_util/test_decorators.py
index f237f88..846c6dd 100644
--- a/tests/contrib/django_util/test_decorators.py
+++ b/tests/contrib/django_util/test_decorators.py
@@ -18,18 +18,18 @@
 
 from django import http
 import django.conf
-from django.contrib.auth import models as django_models
+from django.contrib.auth.models import AnonymousUser, User
 import mock
 from six.moves import http_client
 from six.moves import reload_module
 from six.moves.urllib import parse
+from tests.contrib.django_util import TestWithDjangoEnvironment
 
 import oauth2client.contrib.django_util
 from oauth2client.contrib.django_util import decorators
-from tests.contrib import django_util as tests_django_util
 
 
-class OAuth2EnabledDecoratorTest(tests_django_util.TestWithDjangoEnvironment):
+class OAuth2EnabledDecoratorTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(OAuth2EnabledDecoratorTest, self).setUp()
@@ -39,7 +39,7 @@
         # at import time, so in order for us to reload the settings
         # we need to reload the module
         reload_module(oauth2client.contrib.django_util)
-        self.user = django_models.User.objects.create_user(
+        self.user = User.objects.create_user(
             username='bill', email='bill@example.com', password='hunter2')
 
     def tearDown(self):
@@ -63,7 +63,7 @@
     @mock.patch('oauth2client.client.OAuth2Credentials')
     def test_has_credentials_in_storage(self, OAuth2Credentials):
         request = self.factory.get('/test')
-        request.session = mock.Mock()
+        request.session = mock.MagicMock()
 
         credentials_mock = mock.Mock(
             scopes=set(django.conf.settings.GOOGLE_OAUTH2_SCOPES))
@@ -88,11 +88,11 @@
     @mock.patch('oauth2client.contrib.dictionary_storage.DictionaryStorage')
     def test_specified_scopes(self, dictionary_storage_mock):
         request = self.factory.get('/test')
-        request.session = mock.Mock()
+        request.session = mock.MagicMock()
 
         credentials_mock = mock.Mock(
             scopes=set(django.conf.settings.GOOGLE_OAUTH2_SCOPES))
-        credentials_mock.has_scopes = mock.Mock(return_value=True)
+        credentials_mock.has_scopes = True
         credentials_mock.is_valid = True
         dictionary_storage_mock.get.return_value = credentials_mock
 
@@ -106,14 +106,14 @@
         self.assertFalse(request.oauth.has_credentials())
 
 
-class OAuth2RequiredDecoratorTest(tests_django_util.TestWithDjangoEnvironment):
+class OAuth2RequiredDecoratorTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(OAuth2RequiredDecoratorTest, self).setUp()
         self.save_settings = copy.deepcopy(django.conf.settings)
 
         reload_module(oauth2client.contrib.django_util)
-        self.user = django_models.User.objects.create_user(
+        self.user = User.objects.create_user(
             username='bill', email='bill@example.com', password='hunter2')
 
     def tearDown(self):
@@ -141,13 +141,13 @@
     @mock.patch('oauth2client.contrib.django_util.UserOAuth2', autospec=True)
     def test_has_credentials_in_storage(self, UserOAuth2):
         request = self.factory.get('/test')
-        request.session = mock.Mock()
+        request.session = mock.MagicMock()
 
         @decorators.oauth_required
         def test_view(request):
             return http.HttpResponse("test")
 
-        my_user_oauth = mock.Mock()
+        my_user_oauth = mock.MagicMock()
 
         UserOAuth2.return_value = my_user_oauth
         my_user_oauth.has_credentials.return_value = True
@@ -161,7 +161,7 @@
             self, OAuth2Credentials):
         request = self.factory.get('/test')
 
-        request.session = mock.Mock()
+        request.session = mock.MagicMock()
         credentials_mock = mock.Mock(
             scopes=set(django.conf.settings.GOOGLE_OAUTH2_SCOPES))
         credentials_mock.has_scopes.return_value = False
@@ -179,11 +179,11 @@
     @mock.patch('oauth2client.client.OAuth2Credentials')
     def test_specified_scopes(self, OAuth2Credentials):
         request = self.factory.get('/test')
-        request.session = mock.Mock()
+        request.session = mock.MagicMock()
 
         credentials_mock = mock.Mock(
             scopes=set(django.conf.settings.GOOGLE_OAUTH2_SCOPES))
-        credentials_mock.has_scopes = mock.Mock(return_value=False)
+        credentials_mock.has_scopes = False
         OAuth2Credentials.from_json.return_value = credentials_mock
 
         @decorators.oauth_required(scopes=['additional-scope'])
@@ -195,8 +195,7 @@
             response.status_code, django.http.HttpResponseRedirect.status_code)
 
 
-class OAuth2RequiredDecoratorStorageModelTest(
-        tests_django_util.TestWithDjangoEnvironment):
+class OAuth2RequiredDecoratorStorageModelTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(OAuth2RequiredDecoratorStorageModelTest, self).setUp()
@@ -210,7 +209,7 @@
         django.conf.settings.GOOGLE_OAUTH2_STORAGE_MODEL = STORAGE_MODEL
 
         reload_module(oauth2client.contrib.django_util)
-        self.user = django_models.User.objects.create_user(
+        self.user = User.objects.create_user(
             username='bill', email='bill@example.com', password='hunter2')
 
     def tearDown(self):
@@ -220,7 +219,7 @@
     def test_redirects_anonymous_to_login(self):
         request = self.factory.get('/test')
         request.session = self.session
-        request.user = django_models.AnonymousUser()
+        request.user = AnonymousUser()
 
         @decorators.oauth_required
         def test_view(request):
@@ -234,7 +233,7 @@
     def test_redirects_user_to_oauth_authorize(self):
         request = self.factory.get('/test')
         request.session = self.session
-        request.user = django_models.User.objects.create_user(
+        request.user = User.objects.create_user(
             username='bill3', email='bill@example.com', password='hunter2')
 
         @decorators.oauth_required
diff --git a/tests/contrib/django_util/test_django_models.py b/tests/contrib/django_util/test_django_models.py
index da54965..aeaed15 100644
--- a/tests/contrib/django_util/test_django_models.py
+++ b/tests/contrib/django_util/test_django_models.py
@@ -19,42 +19,36 @@
 
 import base64
 import pickle
-import unittest
 
-import jsonpickle
+from tests.contrib.django_util.models import CredentialsModel
 
-from oauth2client import _helpers
-from oauth2client import client
-from oauth2client.contrib.django_util import models
-from tests.contrib.django_util import models as tests_models
+import unittest2
+
+from oauth2client._helpers import _from_bytes
+from oauth2client.client import Credentials
+from oauth2client.contrib.django_util.models import CredentialsField
 
 
-class TestCredentialsField(unittest.TestCase):
+class TestCredentialsField(unittest2.TestCase):
 
     def setUp(self):
-        self.fake_model = tests_models.CredentialsModel()
+        self.fake_model = CredentialsModel()
         self.fake_model_field = self.fake_model._meta.get_field('credentials')
-        self.field = models.CredentialsField(null=True)
-        self.credentials = client.Credentials()
-        self.pickle_str = _helpers._from_bytes(
+        self.field = CredentialsField(null=True)
+        self.credentials = Credentials()
+        self.pickle_str = _from_bytes(
             base64.b64encode(pickle.dumps(self.credentials)))
-        self.jsonpickle_str = _helpers._from_bytes(
-            base64.b64encode(jsonpickle.encode(self.credentials).encode()))
 
     def test_field_is_text(self):
         self.assertEqual(self.field.get_internal_type(), 'BinaryField')
 
     def test_field_unpickled(self):
         self.assertIsInstance(
-            self.field.to_python(self.pickle_str), client.Credentials)
-
-    def test_field_jsonunpickled(self):
-        self.assertIsInstance(
-            self.field.to_python(self.jsonpickle_str), client.Credentials)
+            self.field.to_python(self.pickle_str), Credentials)
 
     def test_field_already_unpickled(self):
         self.assertIsInstance(
-            self.field.to_python(self.credentials), client.Credentials)
+            self.field.to_python(self.credentials), Credentials)
 
     def test_none_field_unpickled(self):
         self.assertIsNone(self.field.to_python(None))
@@ -62,7 +56,7 @@
     def test_from_db_value(self):
         value = self.field.from_db_value(
             self.pickle_str, None, None, None)
-        self.assertIsInstance(value, client.Credentials)
+        self.assertIsInstance(value, Credentials)
 
     def test_field_unpickled_none(self):
         self.assertEqual(self.field.to_python(None), None)
@@ -70,12 +64,12 @@
     def test_field_pickled(self):
         prep_value = self.field.get_db_prep_value(self.credentials,
                                                   connection=None)
-        self.assertEqual(prep_value, self.jsonpickle_str)
+        self.assertEqual(prep_value, self.pickle_str)
 
     def test_field_value_to_string(self):
         self.fake_model.credentials = self.credentials
         value_str = self.fake_model_field.value_to_string(self.fake_model)
-        self.assertEqual(value_str, self.jsonpickle_str)
+        self.assertEqual(value_str, self.pickle_str)
 
     def test_field_value_to_string_none(self):
         self.fake_model.credentials = None
@@ -83,11 +77,11 @@
         self.assertIsNone(value_str)
 
     def test_credentials_without_null(self):
-        credentials = models.CredentialsField()
+        credentials = CredentialsField()
         self.assertTrue(credentials.null)
 
 
-class CredentialWithSetStore(models.CredentialsField):
+class CredentialWithSetStore(CredentialsField):
     def __init__(self):
         self.model = CredentialWithSetStore
 
@@ -102,4 +96,4 @@
 
 class FakeCredentialsModelMockNoSet(object):
 
-    credentials = models.CredentialsField()
+    credentials = CredentialsField()
diff --git a/tests/contrib/django_util/test_django_storage.py b/tests/contrib/django_util/test_django_storage.py
index a608c94..8f76b18 100644
--- a/tests/contrib/django_util/test_django_storage.py
+++ b/tests/contrib/django_util/test_django_storage.py
@@ -16,10 +16,10 @@
 
 # Mock a Django environment
 import datetime
-import unittest
 
 from django.db import models
 import mock
+import unittest2
 
 from oauth2client import GOOGLE_TOKEN_URI
 from oauth2client.client import OAuth2Credentials
@@ -28,7 +28,7 @@
     DjangoORMStorage as Storage)
 
 
-class TestStorage(unittest.TestCase):
+class TestStorage(unittest2.TestCase):
     def setUp(self):
         access_token = 'foo'
         client_id = 'some_client_id'
diff --git a/tests/contrib/django_util/test_django_util.py b/tests/contrib/django_util/test_django_util.py
index 82d7be7..84457cb 100644
--- a/tests/contrib/django_util/test_django_util.py
+++ b/tests/contrib/django_util/test_django_util.py
@@ -15,19 +15,20 @@
 """Tests the initialization logic of django_util."""
 
 import copy
-import unittest
 
 import django.conf
 from django.conf.urls import include, url
-from django.contrib.auth import models as django_models
+from django.contrib.auth.models import AnonymousUser
 from django.core import exceptions
 import mock
 from six.moves import reload_module
+from tests.contrib.django_util import TestWithDjangoEnvironment
+import unittest2
 
 from oauth2client.contrib import django_util
 import oauth2client.contrib.django_util
-from oauth2client.contrib.django_util import site
-from tests.contrib import django_util as tests_django_util
+from oauth2client.contrib.django_util import (
+    _CREDENTIALS_KEY, get_storage, site, UserOAuth2)
 
 
 urlpatterns = [
@@ -35,7 +36,7 @@
 ]
 
 
-class OAuth2SetupTest(unittest.TestCase):
+class OAuth2SetupTest(unittest2.TestCase):
 
     def setUp(self):
         self.save_settings = copy.deepcopy(django.conf.settings)
@@ -100,20 +101,6 @@
                 object.__new__(django_util.OAuth2Settings),
                 django.conf.settings)
 
-    def test_no_middleware(self):
-        django.conf.settings.MIDDLEWARE_CLASSES = None
-        with self.assertRaises(exceptions.ImproperlyConfigured):
-            django_util.OAuth2Settings.__init__(
-                object.__new__(django_util.OAuth2Settings),
-                django.conf.settings)
-
-    def test_middleware_no_classes(self):
-        django.conf.settings.MIDDLEWARE = (
-            django.conf.settings.MIDDLEWARE_CLASSES)
-        django.conf.settings.MIDDLEWARE_CLASSES = None
-        # primarily testing this doesn't raise an exception
-        django_util.OAuth2Settings(django.conf.settings)
-
     def test_storage_model(self):
         STORAGE_MODEL = {
             'model': 'tests.contrib.django_util.models.CredentialsModel',
@@ -134,7 +121,7 @@
         self.session = session
 
 
-class SessionStorageTest(tests_django_util.TestWithDjangoEnvironment):
+class SessionStorageTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(SessionStorageTest, self).setUp()
@@ -146,19 +133,19 @@
         django.conf.settings = copy.deepcopy(self.save_settings)
 
     def test_session_delete(self):
-        self.session[django_util._CREDENTIALS_KEY] = "test_val"
+        self.session[_CREDENTIALS_KEY] = "test_val"
         request = MockObjectWithSession(self.session)
-        django_storage = django_util.get_storage(request)
+        django_storage = get_storage(request)
         django_storage.delete()
-        self.assertIsNone(self.session.get(django_util._CREDENTIALS_KEY))
+        self.assertIsNone(self.session.get(_CREDENTIALS_KEY))
 
     def test_session_delete_nothing(self):
         request = MockObjectWithSession(self.session)
-        django_storage = django_util.get_storage(request)
+        django_storage = get_storage(request)
         django_storage.delete()
 
 
-class TestUserOAuth2Object(tests_django_util.TestWithDjangoEnvironment):
+class TestUserOAuth2Object(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(TestUserOAuth2Object, self).setUp()
@@ -180,6 +167,6 @@
         request = self.factory.get('oauth2/oauth2authorize',
                                    data={'return_url': '/return_endpoint'})
         request.session = self.session
-        request.user = django_models.AnonymousUser()
-        oauth2 = django_util.UserOAuth2(request)
+        request.user = AnonymousUser()
+        oauth2 = UserOAuth2(request)
         self.assertIsNone(oauth2.credentials)
diff --git a/tests/contrib/django_util/test_views.py b/tests/contrib/django_util/test_views.py
index 0b3fe30..df0d11c 100644
--- a/tests/contrib/django_util/test_views.py
+++ b/tests/contrib/django_util/test_views.py
@@ -20,25 +20,27 @@
 import django
 from django import http
 import django.conf
-from django.contrib.auth import models as django_models
+from django.contrib.auth.models import AnonymousUser, User
 import mock
 from six.moves import reload_module
 
-from oauth2client import client
+from tests.contrib.django_util import TestWithDjangoEnvironment
+from tests.contrib.django_util.models import CredentialsModel
+
+from oauth2client.client import FlowExchangeError, OAuth2WebServerFlow
 import oauth2client.contrib.django_util
 from oauth2client.contrib.django_util import views
-from tests.contrib import django_util as tests_django_util
-from tests.contrib.django_util import models as tests_models
+from oauth2client.contrib.django_util.models import CredentialsField
 
 
-class OAuth2AuthorizeTest(tests_django_util.TestWithDjangoEnvironment):
+class OAuth2AuthorizeTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(OAuth2AuthorizeTest, self).setUp()
         self.save_settings = copy.deepcopy(django.conf.settings)
         reload_module(oauth2client.contrib.django_util)
-        self.user = django_models.User.objects.create_user(
-            username='bill', email='bill@example.com', password='hunter2')
+        self.user = User.objects.create_user(
+          username='bill', email='bill@example.com', password='hunter2')
 
     def tearDown(self):
         django.conf.settings = copy.deepcopy(self.save_settings)
@@ -53,7 +55,7 @@
     def test_authorize_anonymous_user(self):
         request = self.factory.get('oauth2/oauth2authorize')
         request.session = self.session
-        request.user = django_models.AnonymousUser()
+        request.user = AnonymousUser()
         response = views.oauth2_authorize(request)
         self.assertIsInstance(response, http.HttpResponseRedirect)
 
@@ -66,8 +68,7 @@
         self.assertIsInstance(response, http.HttpResponseRedirect)
 
 
-class Oauth2AuthorizeStorageModelTest(
-        tests_django_util.TestWithDjangoEnvironment):
+class Oauth2AuthorizeStorageModelTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(Oauth2AuthorizeStorageModelTest, self).setUp()
@@ -84,7 +85,7 @@
         # at import time, so in order for us to reload the settings
         # we need to reload the module
         reload_module(oauth2client.contrib.django_util)
-        self.user = django_models.User.objects.create_user(
+        self.user = User.objects.create_user(
             username='bill', email='bill@example.com', password='hunter2')
 
     def tearDown(self):
@@ -102,7 +103,7 @@
     def test_authorize_anonymous_user_redirects_login(self):
         request = self.factory.get('oauth2/oauth2authorize')
         request.session = self.session
-        request.user = django_models.AnonymousUser()
+        request.user = AnonymousUser()
         response = views.oauth2_authorize(request)
         self.assertIsInstance(response, http.HttpResponseRedirect)
         # redirects to Django login
@@ -116,53 +117,25 @@
         response = views.oauth2_authorize(request)
         self.assertIsInstance(response, http.HttpResponseRedirect)
 
-    def test_authorized_user_no_credentials_redirects(self):
+    def test_authorized_user_not_logged_in_redirects(self):
         request = self.factory.get('oauth2/oauth2authorize',
                                    data={'return_url': '/return_endpoint'})
         request.session = self.session
 
-        authorized_user = django_models.User.objects.create_user(
+        authorized_user = User.objects.create_user(
             username='bill2', email='bill@example.com', password='hunter2')
+        credentials = CredentialsField()
 
-        tests_models.CredentialsModel.objects.create(
-            user_id=authorized_user,
-            credentials=None)
-
-        request.user = authorized_user
-        response = views.oauth2_authorize(request)
-        self.assertIsInstance(response, http.HttpResponseRedirect)
-
-    def test_already_authorized(self):
-        request = self.factory.get('oauth2/oauth2authorize',
-                                   data={'return_url': '/return_endpoint'})
-        request.session = self.session
-
-        authorized_user = django_models.User.objects.create_user(
-            username='bill2', email='bill@example.com', password='hunter2')
-
-        credentials = _Credentials()
-        tests_models.CredentialsModel.objects.create(
+        CredentialsModel.objects.create(
             user_id=authorized_user,
             credentials=credentials)
 
         request.user = authorized_user
         response = views.oauth2_authorize(request)
         self.assertIsInstance(response, http.HttpResponseRedirect)
-        self.assertEqual(response.url, '/return_endpoint')
 
 
-class _Credentials(object):
-    # Can't use mock when testing Django models
-    # https://code.djangoproject.com/ticket/25493
-    def __init__(self):
-        self.invalid = False
-        self.scopes = set()
-
-    def has_scopes(self, _):
-        return True
-
-
-class Oauth2CallbackTest(tests_django_util.TestWithDjangoEnvironment):
+class Oauth2CallbackTest(TestWithDjangoEnvironment):
 
     def setUp(self):
         super(Oauth2CallbackTest, self).setUp()
@@ -176,11 +149,11 @@
             'return_url': self.RETURN_URL,
             'scopes': django.conf.settings.GOOGLE_OAUTH2_SCOPES
         }
-        self.user = django_models.User.objects.create_user(
+        self.user = User.objects.create_user(
             username='bill', email='bill@example.com', password='hunter2')
 
-    @mock.patch('oauth2client.contrib.django_util.views.jsonpickle')
-    def test_callback_works(self, jsonpickle_mock):
+    @mock.patch('oauth2client.contrib.django_util.views.pickle')
+    def test_callback_works(self, pickle):
         request = self.factory.get('oauth2/oauth2callback', data={
             'state': json.dumps(self.fake_state),
             'code': 123
@@ -188,7 +161,7 @@
 
         self.session['google_oauth2_csrf_token'] = self.CSRF_TOKEN
 
-        flow = client.OAuth2WebServerFlow(
+        flow = OAuth2WebServerFlow(
             client_id='clientid',
             client_secret='clientsecret',
             scope=['email'],
@@ -196,10 +169,9 @@
             redirect_uri=request.build_absolute_uri("oauth2/oauth2callback"))
 
         name = 'google_oauth2_flow_{0}'.format(self.CSRF_TOKEN)
-        pickled_flow = object()
-        self.session[name] = pickled_flow
+        self.session[name] = pickle.dumps(flow)
         flow.step2_exchange = mock.Mock()
-        jsonpickle_mock.decode.return_value = flow
+        pickle.loads.return_value = flow
 
         request.session = self.session
         request.user = self.user
@@ -208,10 +180,9 @@
         self.assertEqual(
             response.status_code, django.http.HttpResponseRedirect.status_code)
         self.assertEqual(response['Location'], self.RETURN_URL)
-        jsonpickle_mock.decode.assert_called_once_with(pickled_flow)
 
-    @mock.patch('oauth2client.contrib.django_util.views.jsonpickle')
-    def test_callback_handles_bad_flow_exchange(self, jsonpickle_mock):
+    @mock.patch('oauth2client.contrib.django_util.views.pickle')
+    def test_callback_handles_bad_flow_exchange(self, pickle):
         request = self.factory.get('oauth2/oauth2callback', data={
             "state": json.dumps(self.fake_state),
             "code": 123
@@ -219,27 +190,25 @@
 
         self.session['google_oauth2_csrf_token'] = self.CSRF_TOKEN
 
-        flow = client.OAuth2WebServerFlow(
+        flow = OAuth2WebServerFlow(
             client_id='clientid',
             client_secret='clientsecret',
             scope=['email'],
             state=json.dumps(self.fake_state),
             redirect_uri=request.build_absolute_uri('oauth2/oauth2callback'))
 
-        session_key = 'google_oauth2_flow_{0}'.format(self.CSRF_TOKEN)
-        pickled_flow = object()
-        self.session[session_key] = pickled_flow
+        self.session['google_oauth2_flow_{0}'.format(self.CSRF_TOKEN)] \
+            = pickle.dumps(flow)
 
         def local_throws(code):
-            raise client.FlowExchangeError('test')
+            raise FlowExchangeError('test')
 
         flow.step2_exchange = local_throws
-        jsonpickle_mock.decode.return_value = flow
+        pickle.loads.return_value = flow
 
         request.session = self.session
         response = views.oauth2_callback(request)
         self.assertIsInstance(response, http.HttpResponseBadRequest)
-        jsonpickle_mock.decode.assert_called_once_with(pickled_flow)
 
     def test_error_returns_bad_request(self):
         request = self.factory.get('oauth2/oauth2callback', data={
@@ -249,15 +218,6 @@
         self.assertIsInstance(response, http.HttpResponseBadRequest)
         self.assertIn(b'Authorization failed', response.content)
 
-    def test_error_escapes_html(self):
-        request = self.factory.get('oauth2/oauth2callback', data={
-            'error': '<script>bad</script>',
-        })
-        response = views.oauth2_callback(request)
-        self.assertIsInstance(response, http.HttpResponseBadRequest)
-        self.assertNotIn(b'<script>', response.content)
-        self.assertIn(b'&lt;script&gt;', response.content)
-
     def test_no_session(self):
         request = self.factory.get('oauth2/oauth2callback', data={
             'code': 123,
diff --git a/tests/contrib/appengine/test__appengine_ndb.py b/tests/contrib/test__appengine_ndb.py
similarity index 96%
rename from tests/contrib/appengine/test__appengine_ndb.py
rename to tests/contrib/test__appengine_ndb.py
index 9af1dcc..41e3805 100644
--- a/tests/contrib/appengine/test__appengine_ndb.py
+++ b/tests/contrib/test__appengine_ndb.py
@@ -14,17 +14,17 @@
 
 import json
 import os
-import unittest
 
 from google.appengine.ext import ndb
 from google.appengine.ext import testbed
 import mock
+import unittest2
 
 from oauth2client import client
 from oauth2client.contrib import appengine
 
 
-DATA_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'data')
+DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data')
 
 
 def datafile(filename):
@@ -36,7 +36,7 @@
     creds = appengine.CredentialsNDBProperty()
 
 
-class TestFlowNDBProperty(unittest.TestCase):
+class TestFlowNDBProperty(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
@@ -85,7 +85,7 @@
                                                  type(flow_val))
 
 
-class TestCredentialsNDBProperty(unittest.TestCase):
+class TestCredentialsNDBProperty(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
diff --git a/tests/contrib/appengine/test_appengine.py b/tests/contrib/test_appengine.py
similarity index 88%
rename from tests/contrib/appengine/test_appengine.py
rename to tests/contrib/test_appengine.py
index 36d2713..cdaf6c5 100644
--- a/tests/contrib/appengine/test_appengine.py
+++ b/tests/contrib/test_appengine.py
@@ -17,7 +17,10 @@
 import os
 import tempfile
 import time
-import unittest
+
+import dev_appserver
+
+dev_appserver.fix_sys_path()
 
 from google.appengine.api import apiproxy_stub
 from google.appengine.api import apiproxy_stub_map
@@ -28,9 +31,10 @@
 from google.appengine.ext import db
 from google.appengine.ext import ndb
 from google.appengine.ext import testbed
+import httplib2
 import mock
 from six.moves import urllib
-from six.moves import urllib_parse
+import unittest2
 import webapp2
 from webtest import TestApp
 
@@ -38,20 +42,11 @@
 from oauth2client import client
 from oauth2client import clientsecrets
 from oauth2client.contrib import appengine
-from tests import http_mock
+from ..http_mock import CacheMock
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
-DATA_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'data')
-DEFAULT_RESP = """\
-{
-    "access_token": "foo_access_token",
-    "expires_in": 3600,
-    "extra": "value",
-    "refresh_token": "foo_refresh_token"
-}
-"""
-BASIC_TOKEN = 'bar'
-BASIC_RESP = json.dumps({'access_token': BASIC_TOKEN})
+DATA_DIR = os.path.join(os.path.dirname(__file__), '..', 'data')
 
 
 def datafile(filename):
@@ -80,7 +75,23 @@
         return None
 
 
-class TestAppAssertionCredentials(unittest.TestCase):
+class Http2Mock(object):
+    """Mock httplib2.Http"""
+    status = 200
+    content = {
+        'access_token': 'foo_access_token',
+        'refresh_token': 'foo_refresh_token',
+        'expires_in': 3600,
+        'extra': 'value',
+    }
+
+    def request(self, token_uri, method, body, headers, *args, **kwargs):
+        self.body = body
+        self.headers = headers
+        return self, json.dumps(self.content)
+
+
+class TestAppAssertionCredentials(unittest2.TestCase):
     account_name = "service_account_name@appspot.com"
     signature = "signature"
 
@@ -128,7 +139,7 @@
 
         scope = 'http://www.googleapis.com/scope'
         credentials = appengine.AppAssertionCredentials(scope)
-        http = http_mock.HttpMock(data=DEFAULT_RESP)
+        http = httplib2.Http()
         with self.assertRaises(client.AccessTokenRefreshError):
             credentials.refresh(http)
 
@@ -144,7 +155,7 @@
             "http://www.googleapis.com/scope",
             "http://www.googleapis.com/scope2"]
         credentials = appengine.AppAssertionCredentials(scope)
-        http = http_mock.HttpMock(data=DEFAULT_RESP)
+        http = httplib2.Http()
         credentials.refresh(http)
         self.assertEqual('a_token_123', credentials.access_token)
 
@@ -157,7 +168,7 @@
         scope = ('http://www.googleapis.com/scope '
                  'http://www.googleapis.com/scope2')
         credentials = appengine.AppAssertionCredentials(scope)
-        http = http_mock.HttpMock(data=DEFAULT_RESP)
+        http = httplib2.Http()
         credentials.refresh(http)
         self.assertEqual('a_token_123', credentials.access_token)
         self.assertEqual(
@@ -173,7 +184,7 @@
                                autospec=True) as get_access_token:
             credentials = appengine.AppAssertionCredentials(
                 scope, service_account_id=account_id)
-            http = http_mock.HttpMock(data=DEFAULT_RESP)
+            http = httplib2.Http()
             credentials.refresh(http)
 
             self.assertEqual('a_token_456', credentials.access_token)
@@ -265,7 +276,7 @@
     flow = appengine.FlowProperty()
 
 
-class FlowPropertyTest(unittest.TestCase):
+class FlowPropertyTest(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
@@ -304,7 +315,7 @@
     credentials = appengine.CredentialsProperty()
 
 
-class CredentialsPropertyTest(unittest.TestCase):
+class CredentialsPropertyTest(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
@@ -358,7 +369,14 @@
             appengine.CredentialsProperty().validate(42)
 
 
-class StorageByKeyNameTest(unittest.TestCase):
+def _http_request(*args, **kwargs):
+    resp = httplib2.Response({'status': '200'})
+    content = json.dumps({'access_token': 'bar'})
+
+    return resp, content
+
+
+class StorageByKeyNameTest(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
@@ -402,23 +420,6 @@
         storage._model = appengine.CredentialsNDBModel
         self.assertTrue(storage._is_ndb())
 
-    def _verify_basic_refresh(self, http):
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, oauth2client.GOOGLE_TOKEN_URI)
-        self.assertEqual(http.method, 'POST')
-        expected_body = {
-            'grant_type': ['refresh_token'],
-            'client_id': [self.credentials.client_id],
-            'client_secret': [self.credentials.client_secret],
-            'refresh_token': [self.credentials.refresh_token],
-        }
-        self.assertEqual(urllib_parse.parse_qs(http.body), expected_body)
-        expected_headers = {
-            'content-type': 'application/x-www-form-urlencoded',
-            'user-agent': self.credentials.user_agent,
-        }
-        self.assertEqual(http.headers, expected_headers)
-
     def test_get_and_put_simple(self):
         storage = appengine.StorageByKeyName(
             appengine.CredentialsModel, 'foo', 'credentials')
@@ -426,12 +427,9 @@
         self.assertEqual(None, storage.get())
         self.credentials.set_store(storage)
 
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        self.credentials._refresh(http)
+        self.credentials._refresh(_http_request)
         credmodel = appengine.CredentialsModel.get_by_key_name('foo')
-        self.assertEqual(BASIC_TOKEN, credmodel.credentials.access_token)
-        # Verify mock.
-        self._verify_basic_refresh(http)
+        self.assertEqual('bar', credmodel.credentials.access_token)
 
     def test_get_and_put_cached(self):
         storage = appengine.StorageByKeyName(
@@ -440,17 +438,16 @@
         self.assertEqual(None, storage.get())
         self.credentials.set_store(storage)
 
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        self.credentials._refresh(http)
+        self.credentials._refresh(_http_request)
         credmodel = appengine.CredentialsModel.get_by_key_name('foo')
-        self.assertEqual(BASIC_TOKEN, credmodel.credentials.access_token)
+        self.assertEqual('bar', credmodel.credentials.access_token)
 
         # Now remove the item from the cache.
         memcache.delete('foo')
 
         # Check that getting refreshes the cache.
         credentials = storage.get()
-        self.assertEqual(BASIC_TOKEN, credentials.access_token)
+        self.assertEqual('bar', credentials.access_token)
         self.assertNotEqual(None, memcache.get('foo'))
 
         # Deleting should clear the cache.
@@ -459,9 +456,6 @@
         self.assertEqual(None, credentials)
         self.assertEqual(None, memcache.get('foo'))
 
-        # Verify mock.
-        self._verify_basic_refresh(http)
-
     def test_get_and_put_set_store_on_cache_retrieval(self):
         storage = appengine.StorageByKeyName(
             appengine.CredentialsModel, 'foo', 'credentials', cache=memcache)
@@ -474,13 +468,9 @@
         old_creds = storage.get()
         self.assertEqual(old_creds.access_token, 'foo')
         old_creds.invalid = True
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        old_creds._refresh(http)
+        old_creds._refresh(_http_request)
         new_creds = storage.get()
-        self.assertEqual(new_creds.access_token, BASIC_TOKEN)
-
-        # Verify mock.
-        self._verify_basic_refresh(http)
+        self.assertEqual(new_creds.access_token, 'bar')
 
     def test_get_and_put_ndb(self):
         # Start empty
@@ -490,16 +480,12 @@
 
         # Refresh storage and retrieve without using storage
         self.credentials.set_store(storage)
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        self.credentials._refresh(http)
+        self.credentials._refresh(_http_request)
         credmodel = appengine.CredentialsNDBModel.get_by_id('foo')
-        self.assertEqual(BASIC_TOKEN, credmodel.credentials.access_token)
+        self.assertEqual('bar', credmodel.credentials.access_token)
         self.assertEqual(credmodel.credentials.to_json(),
                          self.credentials.to_json())
 
-        # Verify mock.
-        self._verify_basic_refresh(http)
-
     def test_delete_ndb(self):
         # Start empty
         storage = appengine.StorageByKeyName(
@@ -525,18 +511,14 @@
 
         # Set NDB store and refresh to add to storage
         self.credentials.set_store(storage)
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        self.credentials._refresh(http)
+        self.credentials._refresh(_http_request)
 
         # Retrieve same key from DB model to confirm mixing works
         credmodel = appengine.CredentialsModel.get_by_key_name('foo')
-        self.assertEqual(BASIC_TOKEN, credmodel.credentials.access_token)
+        self.assertEqual('bar', credmodel.credentials.access_token)
         self.assertEqual(self.credentials.to_json(),
                          credmodel.credentials.to_json())
 
-        # Verify mock.
-        self._verify_basic_refresh(http)
-
     def test_get_and_put_mixed_db_storage_ndb_get(self):
         # Start empty
         storage = appengine.StorageByKeyName(
@@ -545,18 +527,14 @@
 
         # Set DB store and refresh to add to storage
         self.credentials.set_store(storage)
-        http = http_mock.HttpMock(data=BASIC_RESP)
-        self.credentials._refresh(http)
+        self.credentials._refresh(_http_request)
 
         # Retrieve same key from NDB model to confirm mixing works
         credmodel = appengine.CredentialsNDBModel.get_by_id('foo')
-        self.assertEqual(BASIC_TOKEN, credmodel.credentials.access_token)
+        self.assertEqual('bar', credmodel.credentials.access_token)
         self.assertEqual(self.credentials.to_json(),
                          credmodel.credentials.to_json())
 
-        # Verify mock.
-        self._verify_basic_refresh(http)
-
     def test_delete_db_ndb_mixed(self):
         # Start empty
         storage_ndb = appengine.StorageByKeyName(
@@ -595,7 +573,7 @@
     request = MockRequest()
 
 
-class DecoratorTests(unittest.TestCase):
+class DecoratorTests(unittest2.TestCase):
 
     def setUp(self):
         self.testbed = testbed.Testbed()
@@ -652,9 +630,12 @@
         })
         self.current_user = user_mock()
         users.get_current_user = self.current_user
+        self.httplib2_orig = httplib2.Http
+        httplib2.Http = Http2Mock
 
     def tearDown(self):
         self.testbed.deactivate()
+        httplib2.Http = self.httplib2_orig
 
     def test_in_error(self):
         # NOTE: This branch is never reached. _in_error is not set by any code
@@ -674,9 +655,7 @@
             app.router.match_routes[0].handler.__name__,
             'OAuth2Handler')
 
-    @mock.patch('oauth2client.transport.get_http_object')
-    def test_required(self, new_http):
-        new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
+    def test_required(self):
         # An initial request to an oauth_required decorated path should be a
         # redirect to start the OAuth dance.
         self.assertEqual(self.decorator.flow, None)
@@ -709,7 +688,7 @@
                 response_query = urllib.parse.parse_qs(parts[1])
                 response = response_query[
                     self.decorator._token_response_param][0]
-                self.assertEqual(json.loads(DEFAULT_RESP),
+                self.assertEqual(Http2Mock.content,
                                  json.loads(urllib.parse.unquote(response)))
             self.assertEqual(self.decorator.flow, self.decorator._tls.flow)
             self.assertEqual(self.decorator.credentials,
@@ -757,12 +736,7 @@
         self.assertEqual('http://localhost/oauth2callback',
                          query_params['redirect_uri'][0])
 
-        # Check the mocks were called.
-        new_http.assert_called_once_with()
-
-    @mock.patch('oauth2client.transport.get_http_object')
-    def test_storage_delete(self, new_http):
-        new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
+    def test_storage_delete(self):
         # An initial request to an oauth_required decorated path should be a
         # redirect to start the OAuth dance.
         response = self.app.get('/foo_path')
@@ -798,12 +772,7 @@
             parse_state_value.assert_called_once_with(
                 'foo_path:xsrfkey123', self.current_user)
 
-        # Check the mocks were called.
-        new_http.assert_called_once_with()
-
-    @mock.patch('oauth2client.transport.get_http_object')
-    def test_aware(self, new_http):
-        new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
+    def test_aware(self):
         # An initial request to an oauth_aware decorated path should
         # not redirect.
         response = self.app.get('http://localhost/bar_path/2012/01')
@@ -856,9 +825,6 @@
         self.should_raise = False
         self.assertEqual(None, self.decorator.credentials)
 
-        # Check the mocks were called.
-        new_http.assert_called_once_with()
-
     def test_error_in_step2(self):
         # An initial request to an oauth_aware decorated path should
         # not redirect.
@@ -889,14 +855,10 @@
         self.assertEqual(decorator.flow, decorator._tls.flow)
 
     def test_token_response_param(self):
-        # No need to set-up a mock since test_required() does.
         self.decorator._token_response_param = 'foobar'
         self.test_required()
 
-    @mock.patch('oauth2client.transport.get_http_object')
-    def test_decorator_from_client_secrets(self, new_http):
-        new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
-        # Execute test after setting up mock.
+    def test_decorator_from_client_secrets(self):
         decorator = appengine.OAuth2DecoratorFromClientSecrets(
             datafile('client_secrets.json'),
             scope=['foo_scope', 'bar_scope'])
@@ -911,13 +873,10 @@
 
         # revoke_uri is not required
         self.assertEqual(self.decorator._revoke_uri,
-                         'https://oauth2.googleapis.com/revoke')
+                         'https://accounts.google.com/o/oauth2/revoke')
         self.assertEqual(self.decorator._revoke_uri,
                          self.decorator.credentials.revoke_uri)
 
-        # Check the mocks were called.
-        new_http.assert_called_once_with()
-
     def test_decorator_from_client_secrets_toplevel(self):
         decorator_patch = mock.patch(
             'oauth2client.contrib.appengine.OAuth2DecoratorFromClientSecrets')
@@ -956,7 +915,7 @@
         self.assertIn('prompt', decorator._kwargs)
 
     def test_decorator_from_cached_client_secrets(self):
-        cache_mock = http_mock.CacheMock()
+        cache_mock = CacheMock()
         load_and_cache('client_secrets.json', 'secret', cache_mock)
         decorator = appengine.OAuth2DecoratorFromClientSecrets(
             # filename, scope, message=None, cache=None
@@ -1016,11 +975,11 @@
             'oauth2client.contrib.appengine.clientsecrets.loadfile')
         with loadfile_patch as loadfile_mock:
             loadfile_mock.return_value = (clientsecrets.TYPE_WEB, {
-                'client_id': 'foo_client_id',
-                'client_secret': 'foo_client_secret',
-                'redirect_uris': [],
-                'auth_uri': oauth2client.GOOGLE_AUTH_URI,
-                'token_uri': oauth2client.GOOGLE_TOKEN_URI,
+                "client_id": "foo_client_id",
+                "client_secret": "foo_client_secret",
+                "redirect_uris": [],
+                "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
+                "token_uri": "https://www.googleapis.com/oauth2/v4/token",
                 # No revoke URI
             })
 
@@ -1032,10 +991,7 @@
         # This is never set, but it's consistent with other tests.
         self.assertFalse(decorator._in_error)
 
-    @mock.patch('oauth2client.transport.get_http_object')
-    def test_invalid_state(self, new_http):
-        new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
-        # Execute test after setting up mock.
+    def test_invalid_state(self):
         with mock.patch.object(appengine, '_parse_state_value',
                                return_value=None, autospec=True):
             # Now simulate the callback to /oauth2callback.
@@ -1046,11 +1002,8 @@
             self.assertEqual('200 OK', response.status)
             self.assertEqual('The authorization request failed', response.body)
 
-        # Check the mocks were called.
-        new_http.assert_called_once_with()
 
-
-class DecoratorXsrfSecretTests(unittest.TestCase):
+class DecoratorXsrfSecretTests(unittest2.TestCase):
     """Test xsrf_secret_key."""
 
     def setUp(self):
@@ -1099,7 +1052,7 @@
         self.assertEqual(site_key.secret, secret)
 
 
-class DecoratorXsrfProtectionTests(unittest.TestCase):
+class DecoratorXsrfProtectionTests(unittest2.TestCase):
     """Test _build_state_value and _parse_state_value."""
 
     def setUp(self):
diff --git a/tests/contrib/test_devshell.py b/tests/contrib/test_devshell.py
index 1346080..659a53b 100644
--- a/tests/contrib/test_devshell.py
+++ b/tests/contrib/test_devshell.py
@@ -19,9 +19,9 @@
 import os
 import socket
 import threading
-import unittest
 
 import mock
+import unittest2
 
 from oauth2client import _helpers
 from oauth2client import client
@@ -38,7 +38,7 @@
 ])
 
 
-class TestCredentialInfoResponse(unittest.TestCase):
+class TestCredentialInfoResponse(unittest2.TestCase):
 
     def test_constructor_with_non_list(self):
         json_non_list = '{}'
@@ -71,29 +71,29 @@
         self.assertEqual(info_response.expires_in, expires_in)
 
 
-class Test_SendRecv(unittest.TestCase):
+class Test_SendRecv(unittest2.TestCase):
 
     def test_port_zero(self):
         with mock.patch('oauth2client.contrib.devshell.os') as os_mod:
-            os_mod.getenv = mock.Mock(name='getenv', return_value=0)
+            os_mod.getenv = mock.MagicMock(name='getenv', return_value=0)
             with self.assertRaises(devshell.NoDevshellServer):
                 devshell._SendRecv()
             os_mod.getenv.assert_called_once_with(devshell.DEVSHELL_ENV, 0)
 
     def test_no_newline_in_received_header(self):
         non_zero_port = 1
-        sock = mock.Mock()
+        sock = mock.MagicMock()
 
         header_without_newline = ''
-        sock.recv(6).decode = mock.Mock(
+        sock.recv(6).decode = mock.MagicMock(
             name='decode', return_value=header_without_newline)
 
         with mock.patch('oauth2client.contrib.devshell.os') as os_mod:
-            os_mod.getenv = mock.Mock(name='getenv',
-                                      return_value=non_zero_port)
+            os_mod.getenv = mock.MagicMock(name='getenv',
+                                           return_value=non_zero_port)
             with mock.patch('oauth2client.contrib.devshell.socket') as socket:
-                socket.socket = mock.Mock(name='socket',
-                                          return_value=sock)
+                socket.socket = mock.MagicMock(name='socket',
+                                               return_value=sock)
                 with self.assertRaises(devshell.CommunicationError):
                     devshell._SendRecv()
                 os_mod.getenv.assert_called_once_with(devshell.DEVSHELL_ENV, 0)
@@ -160,15 +160,15 @@
                     s.recv(to_read, socket.MSG_WAITALL))
             if resp_buffer != devshell.CREDENTIAL_INFO_REQUEST_JSON:
                 self.bad_request = True
-            response_len = len(self.response)
-            s.sendall('{0}\n{1}'.format(response_len, self.response).encode())
+            l = len(self.response)
+            s.sendall('{0}\n{1}'.format(l, self.response).encode())
         finally:
             # Will fail if s is None, but these tests never encounter
             # that scenario.
             s.close()
 
 
-class DevshellCredentialsTests(unittest.TestCase):
+class DevshellCredentialsTests(unittest2.TestCase):
 
     def test_signals_no_server(self):
         with self.assertRaises(devshell.NoDevshellServer):
diff --git a/tests/contrib/test_dictionary_storage.py b/tests/contrib/test_dictionary_storage.py
index b9f833b..888c938 100644
--- a/tests/contrib/test_dictionary_storage.py
+++ b/tests/contrib/test_dictionary_storage.py
@@ -14,7 +14,7 @@
 
 """Unit tests for oauth2client.contrib.dictionary_storage"""
 
-import unittest
+import unittest2
 
 import oauth2client
 from oauth2client import client
@@ -37,7 +37,7 @@
         scopes=scopes)
 
 
-class DictionaryStorageTests(unittest.TestCase):
+class DictionaryStorageTests(unittest2.TestCase):
 
     def test_constructor_defaults(self):
         dictionary = {}
diff --git a/tests/contrib/test_flask_util.py b/tests/contrib/test_flask_util.py
index 112bff0..74cb218 100644
--- a/tests/contrib/test_flask_util.py
+++ b/tests/contrib/test_flask_util.py
@@ -17,31 +17,54 @@
 import datetime
 import json
 import logging
-import unittest
 
 import flask
+import httplib2
 import mock
 import six.moves.http_client as httplib
 import six.moves.urllib.parse as urlparse
+import unittest2
 
 import oauth2client
 from oauth2client import client
 from oauth2client import clientsecrets
 from oauth2client.contrib import flask_util
-from tests import http_mock
 
 
-DEFAULT_RESP = """\
-{
-    "access_token": "foo_access_token",
-    "expires_in": 3600,
-    "extra": "value",
-    "refresh_token": "foo_refresh_token"
-}
-"""
+__author__ = 'jonwayne@google.com (Jon Wayne Parrott)'
 
 
-class FlaskOAuth2Tests(unittest.TestCase):
+class Http2Mock(object):
+    """Mock httplib2.Http for code exchange / refresh"""
+
+    def __init__(self, status=httplib.OK, **kwargs):
+        self.status = status
+        self.content = {
+            'access_token': 'foo_access_token',
+            'refresh_token': 'foo_refresh_token',
+            'expires_in': 3600,
+            'extra': 'value',
+        }
+        self.content.update(kwargs)
+
+    def request(self, token_uri, method, body, headers, *args, **kwargs):
+        self.body = body
+        self.headers = headers
+        return (self, json.dumps(self.content).encode('utf-8'))
+
+    def __enter__(self):
+        self.httplib2_orig = httplib2.Http
+        httplib2.Http = self
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        httplib2.Http = self.httplib2_orig
+
+    def __call__(self, *args, **kwargs):
+        return self
+
+
+class FlaskOAuth2Tests(unittest2.TestCase):
 
     def setUp(self):
         self.app = flask.Flask(__name__)
@@ -223,12 +246,7 @@
     def test_callback_view(self):
         self.oauth2.storage = mock.Mock()
         with self.app.test_client() as client:
-            with mock.patch(
-                    'oauth2client.transport.get_http_object') as new_http:
-                # Set-up mock.
-                http = http_mock.HttpMock(data=DEFAULT_RESP)
-                new_http.return_value = http
-                # Run tests.
+            with Http2Mock() as http:
                 state = self._setup_callback_state(client)
 
                 response = client.get(
@@ -240,9 +258,6 @@
                 self.assertIn('codez', http.body)
                 self.assertTrue(self.oauth2.storage.put.called)
 
-                # Check the mocks were called.
-                new_http.assert_called_once_with()
-
     def test_authorize_callback(self):
         self.oauth2.authorize_callback = mock.Mock()
         self.test_callback_view()
@@ -258,18 +273,6 @@
             self.assertEqual(response.status_code, httplib.BAD_REQUEST)
             self.assertIn('something', response.data.decode('utf-8'))
 
-        # Error supplied to callback with html
-        with self.app.test_client() as client:
-            with client.session_transaction() as session:
-                session['google_oauth2_csrf_token'] = 'tokenz'
-
-            response = client.get(
-                '/oauth2callback?state={}&error=<script>something<script>')
-            self.assertEqual(response.status_code, httplib.BAD_REQUEST)
-            self.assertIn(
-                '&lt;script&gt;something&lt;script&gt;',
-                response.data.decode('utf-8'))
-
         # CSRF mismatch
         with self.app.test_client() as client:
             with client.session_transaction() as session:
@@ -293,20 +296,11 @@
         with self.app.test_client() as client:
             state = self._setup_callback_state(client)
 
-            with mock.patch(
-                    'oauth2client.transport.get_http_object') as new_http:
-                # Set-up mock.
-                new_http.return_value = http_mock.HttpMock(
-                    headers={'status': httplib.INTERNAL_SERVER_ERROR},
-                    data=DEFAULT_RESP)
-                # Run tests.
+            with Http2Mock(status=httplib.INTERNAL_SERVER_ERROR):
                 response = client.get(
                     '/oauth2callback?state={0}&code=codez'.format(state))
                 self.assertEqual(response.status_code, httplib.BAD_REQUEST)
 
-                # Check the mocks were called.
-                new_http.assert_called_once_with()
-
         # Invalid state json
         with self.app.test_client() as client:
             with client.session_transaction() as session:
@@ -501,10 +495,7 @@
     def test_incremental_auth_exchange(self):
         self._create_incremental_auth_app()
 
-        with mock.patch('oauth2client.transport.get_http_object') as new_http:
-            # Set-up mock.
-            new_http.return_value = http_mock.HttpMock(data=DEFAULT_RESP)
-            # Run tests.
+        with Http2Mock():
             with self.app.test_client() as client:
                 state = self._setup_callback_state(
                     client,
@@ -520,21 +511,16 @@
                 self.assertTrue(
                     credentials.has_scopes(['email', 'one', 'two']))
 
-            # Check the mocks were called.
-            new_http.assert_called_once_with()
-
     def test_refresh(self):
-        token_val = 'new_token'
-        json_resp = '{"access_token": "%s"}' % (token_val,)
-        http = http_mock.HttpMock(data=json_resp)
         with self.app.test_request_context():
             with mock.patch('flask.session'):
                 self.oauth2.storage.put(self._generate_credentials())
 
-                self.oauth2.credentials.refresh(http)
+                self.oauth2.credentials.refresh(
+                    Http2Mock(access_token='new_token'))
 
                 self.assertEqual(
-                    self.oauth2.storage.get().access_token, token_val)
+                    self.oauth2.storage.get().access_token, 'new_token')
 
     def test_delete(self):
         with self.app.test_request_context():
diff --git a/tests/contrib/test_gce.py b/tests/contrib/test_gce.py
index 5f34995..e71bd44 100644
--- a/tests/contrib/test_gce.py
+++ b/tests/contrib/test_gce.py
@@ -16,28 +16,26 @@
 
 import datetime
 import json
-import os
-import unittest
 
+import httplib2
 import mock
 from six.moves import http_client
-from six.moves import reload_module
+from tests.contrib.test_metadata import request_mock
+import unittest2
 
 from oauth2client import client
-from oauth2client.contrib import _metadata
 from oauth2client.contrib import gce
-from tests import http_mock
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
 SERVICE_ACCOUNT_INFO = {
     'scopes': ['a', 'b'],
     'email': 'a@example.com',
     'aliases': ['default']
 }
-METADATA_PATH = 'instance/service-accounts/a@example.com/token'
 
 
-class AppAssertionCredentialsTests(unittest.TestCase):
+class AppAssertionCredentialsTests(unittest2.TestCase):
 
     def test_constructor(self):
         credentials = gce.AppAssertionCredentials()
@@ -70,7 +68,8 @@
     @mock.patch('oauth2client.contrib._metadata.get_service_account_info',
                 return_value=SERVICE_ACCOUNT_INFO)
     def test_refresh_token(self, get_info, get_token):
-        http_mock = object()
+        http_request = mock.MagicMock()
+        http_mock = mock.MagicMock(request=http_request)
         credentials = gce.AppAssertionCredentials()
         credentials.invalid = False
         credentials.service_account_email = 'a@example.com'
@@ -78,34 +77,26 @@
         credentials.get_access_token(http=http_mock)
         self.assertEqual(credentials.access_token, 'A')
         self.assertTrue(credentials.access_token_expired)
-        get_token.assert_called_with(http_mock,
+        get_token.assert_called_with(http_request,
                                      service_account='a@example.com')
         credentials.get_access_token(http=http_mock)
         self.assertEqual(credentials.access_token, 'B')
         self.assertFalse(credentials.access_token_expired)
-        get_token.assert_called_with(http_mock,
+        get_token.assert_called_with(http_request,
                                      service_account='a@example.com')
         get_info.assert_not_called()
 
     def test_refresh_token_failed_fetch(self):
-        headers = {
-            'status': http_client.NOT_FOUND,
-            'content-type': 'application/json',
-        }
-        response = json.dumps({'access_token': 'a', 'expires_in': 100})
-        http = http_mock.HttpMock(headers=headers, data=response)
+        http_request = request_mock(
+            http_client.NOT_FOUND,
+            'application/json',
+            json.dumps({'access_token': 'a', 'expires_in': 100})
+        )
         credentials = gce.AppAssertionCredentials()
         credentials.invalid = False
         credentials.service_account_email = 'a@example.com'
         with self.assertRaises(client.HttpAccessTokenRefreshError):
-            credentials._refresh(http)
-        # Verify mock.
-        self.assertEqual(http.requests, 1)
-        expected_uri = _metadata.METADATA_ROOT + METADATA_PATH
-        self.assertEqual(http.uri, expected_uri)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
+            credentials._refresh(http_request)
 
     def test_serialization_data(self):
         credentials = gce.AppAssertionCredentials()
@@ -124,7 +115,8 @@
     @mock.patch('oauth2client.contrib._metadata.get_service_account_info',
                 return_value=SERVICE_ACCOUNT_INFO)
     def test_retrieve_scopes(self, metadata):
-        http_mock = object()
+        http_request = mock.MagicMock()
+        http_mock = mock.MagicMock(request=http_request)
         credentials = gce.AppAssertionCredentials()
         self.assertTrue(credentials.invalid)
         self.assertIsNone(credentials.scopes)
@@ -133,18 +125,19 @@
         self.assertFalse(credentials.invalid)
         credentials.retrieve_scopes(http_mock)
         # Assert scopes weren't refetched
-        metadata.assert_called_once_with(http_mock,
+        metadata.assert_called_once_with(http_request,
                                          service_account='default')
 
     @mock.patch('oauth2client.contrib._metadata.get_service_account_info',
-                side_effect=http_client.HTTPException('No Such Email'))
+                side_effect=httplib2.HttpLib2Error('No Such Email'))
     def test_retrieve_scopes_bad_email(self, metadata):
-        http_mock = object()
+        http_request = mock.MagicMock()
+        http_mock = mock.MagicMock(request=http_request)
         credentials = gce.AppAssertionCredentials(email='b@example.com')
-        with self.assertRaises(http_client.HTTPException):
+        with self.assertRaises(httplib2.HttpLib2Error):
             credentials.retrieve_scopes(http_mock)
 
-        metadata.assert_called_once_with(http_mock,
+        metadata.assert_called_once_with(http_request,
                                          service_account='b@example.com')
 
     def test_save_to_well_known_file(self):
@@ -157,19 +150,3 @@
                 client.save_to_well_known_file(credentials)
         finally:
             os.path.isdir = ORIGINAL_ISDIR
-
-    def test_custom_metadata_root_from_env(self):
-        headers = {'content-type': 'application/json'}
-        http = http_mock.HttpMock(headers=headers, data='{}')
-        fake_metadata_root = 'another.metadata.service'
-        os.environ['GCE_METADATA_ROOT'] = fake_metadata_root
-        reload_module(_metadata)
-        try:
-            _metadata.get(http, '')
-        finally:
-            del os.environ['GCE_METADATA_ROOT']
-            reload_module(_metadata)
-        # Verify mock.
-        self.assertEqual(http.requests, 1)
-        expected_uri = 'http://{}/computeMetadata/v1/'.format(fake_metadata_root)
-        self.assertEqual(http.uri, expected_uri)
diff --git a/tests/contrib/test_keyring_storage.py b/tests/contrib/test_keyring_storage.py
index 0f8090d..5d274c0 100644
--- a/tests/contrib/test_keyring_storage.py
+++ b/tests/contrib/test_keyring_storage.py
@@ -16,17 +16,20 @@
 
 import datetime
 import threading
-import unittest
 
 import keyring
 import mock
+import unittest2
 
 import oauth2client
 from oauth2client import client
 from oauth2client.contrib import keyring_storage
 
 
-class KeyringStorageTests(unittest.TestCase):
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+class KeyringStorageTests(unittest2.TestCase):
 
     def test_constructor(self):
         service_name = 'my_unit_test'
@@ -55,15 +58,15 @@
         service_name = 'my_unit_test'
         user_name = 'me'
         mock_content = (object(), 'mock_content')
-        mock_return_creds = mock.Mock()
-        mock_return_creds.set_store = set_store = mock.Mock(
+        mock_return_creds = mock.MagicMock()
+        mock_return_creds.set_store = set_store = mock.MagicMock(
             name='set_store')
         with mock.patch.object(keyring, 'get_password',
                                return_value=mock_content,
                                autospec=True) as get_password:
             class_name = 'oauth2client.client.Credentials'
             with mock.patch(class_name) as MockCreds:
-                MockCreds.new_from_json = new_from_json = mock.Mock(
+                MockCreds.new_from_json = new_from_json = mock.MagicMock(
                     name='new_from_json', return_value=mock_return_creds)
                 store = keyring_storage.Storage(service_name, user_name)
                 credentials = store.locked_get()
@@ -79,9 +82,9 @@
         with mock.patch.object(keyring, 'set_password',
                                return_value=None,
                                autospec=True) as set_password:
-            credentials = mock.Mock()
+            credentials = mock.MagicMock()
             to_json_ret = object()
-            credentials.to_json = to_json = mock.Mock(
+            credentials.to_json = to_json = mock.MagicMock(
                 name='to_json', return_value=to_json_ret)
             store.locked_put(credentials)
             to_json.assert_called_once_with()
diff --git a/tests/contrib/test_locked_file.py b/tests/contrib/test_locked_file.py
new file mode 100644
index 0000000..384bef3
--- /dev/null
+++ b/tests/contrib/test_locked_file.py
@@ -0,0 +1,244 @@
+# Copyright 2016 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.
+
+import errno
+import os
+import sys
+import tempfile
+
+import mock
+import unittest2
+
+from oauth2client.contrib import locked_file
+
+
+class TestOpener(unittest2.TestCase):
+    def _make_one(self):
+        _filehandle, filename = tempfile.mkstemp()
+        os.close(_filehandle)
+        return locked_file._Opener(filename, 'r+', 'r'), filename
+
+    def test_ctor(self):
+        instance, filename = self._make_one()
+        self.assertFalse(instance._locked)
+        self.assertEqual(instance._filename, filename)
+        self.assertEqual(instance._mode, 'r+')
+        self.assertEqual(instance._fallback_mode, 'r')
+        self.assertIsNone(instance._fh)
+        self.assertIsNone(instance._lock_fd)
+
+    def test_is_locked(self):
+        instance, _ = self._make_one()
+        self.assertFalse(instance.is_locked())
+        instance._locked = True
+        self.assertTrue(instance.is_locked())
+
+    def test_file_handle(self):
+        instance, _ = self._make_one()
+        self.assertIsNone(instance.file_handle())
+        fh = mock.Mock()
+        instance._fh = fh
+        self.assertEqual(instance.file_handle(), fh)
+
+    def test_filename(self):
+        instance, filename = self._make_one()
+        self.assertEqual(instance.filename(), filename)
+
+    def test_open_and_lock(self):
+        instance, _ = self._make_one()
+        instance.open_and_lock(1, 1)
+
+    def test_unlock_and_close(self):
+        instance, _ = self._make_one()
+        instance.unlock_and_close()
+
+
+class TestPosixOpener(TestOpener):
+    def _make_one(self):
+        _filehandle, filename = tempfile.mkstemp()
+        os.close(_filehandle)
+        return locked_file._PosixOpener(filename, 'r+', 'r'), filename
+
+    def test_relock_fail(self):
+        instance, _ = self._make_one()
+        instance.open_and_lock(1, 1)
+
+        self.assertTrue(instance.is_locked())
+        self.assertIsNotNone(instance.file_handle())
+        with self.assertRaises(locked_file.AlreadyLockedException):
+            instance.open_and_lock(1, 1)
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    def test_lock_access_error_fallback_mode(self, mock_open):
+        # NOTE: This is a bad case. The behavior here should be that the
+        # error gets re-raised, but the module lets the if statement fall
+        # through.
+        instance, _ = self._make_one()
+        mock_open.side_effect = [IOError(errno.ENOENT, '')]
+        instance.open_and_lock(1, 1)
+
+        self.assertIsNone(instance.file_handle())
+        self.assertTrue(instance.is_locked())
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    def test_lock_non_access_error(self, mock_open):
+        instance, _ = self._make_one()
+        fh_mock = mock.Mock()
+        mock_open.side_effect = [IOError(errno.EACCES, ''), fh_mock]
+        instance.open_and_lock(1, 1)
+
+        self.assertEqual(instance.file_handle(), fh_mock)
+        self.assertFalse(instance.is_locked())
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    def test_lock_unexpected_error(self, mock_open):
+        instance, _ = self._make_one()
+
+        with mock.patch('os.open') as mock_os_open:
+            mock_os_open.side_effect = [OSError(errno.EPERM, '')]
+            with self.assertRaises(OSError):
+                instance.open_and_lock(1, 1)
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    @mock.patch('oauth2client.contrib.locked_file.logger')
+    @mock.patch('time.time')
+    def test_lock_timeout_error(self, mock_time, mock_logger, mock_open):
+        instance, _ = self._make_one()
+        # Make it seem like 10 seconds have passed between calls.
+        mock_time.side_effect = [0, 10]
+
+        with mock.patch('os.open') as mock_os_open:
+            # Raising EEXIST should cause it to try to retry locking.
+            mock_os_open.side_effect = [OSError(errno.EEXIST, '')]
+            instance.open_and_lock(1, 1)
+            self.assertFalse(instance.is_locked())
+            self.assertTrue(mock_logger.warn.called)
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    @mock.patch('oauth2client.contrib.locked_file.logger')
+    @mock.patch('time.time')
+    def test_lock_timeout_error_no_fh(self, mock_time, mock_logger, mock_open):
+        instance, _ = self._make_one()
+        # Make it seem like 10 seconds have passed between calls.
+        mock_time.side_effect = [0, 10]
+        # This will cause the retry loop to enter without a file handle.
+        fh_mock = mock.Mock()
+        mock_open.side_effect = [IOError(errno.ENOENT, ''), fh_mock]
+
+        with mock.patch('os.open') as mock_os_open:
+            # Raising EEXIST should cause it to try to retry locking.
+            mock_os_open.side_effect = [OSError(errno.EEXIST, '')]
+            instance.open_and_lock(1, 1)
+            self.assertFalse(instance.is_locked())
+            self.assertTrue(mock_logger.warn.called)
+            self.assertEqual(instance.file_handle(), fh_mock)
+
+    @mock.patch('oauth2client.contrib.locked_file.open', create=True)
+    @mock.patch('time.time')
+    @mock.patch('time.sleep')
+    def test_lock_retry_success(self, mock_sleep, mock_time, mock_open):
+        instance, _ = self._make_one()
+        # Make it seem like 1 second has passed between calls. Extra values
+        # are needed by the logging module.
+        mock_time.side_effect = [0, 1]
+
+        with mock.patch('os.open') as mock_os_open:
+            # Raising EEXIST should cause it to try to retry locking.
+            mock_os_open.side_effect = [
+                OSError(errno.EEXIST, ''), mock.Mock()]
+            instance.open_and_lock(10, 1)
+            print(mock_os_open.call_args_list)
+            self.assertTrue(instance.is_locked())
+            mock_sleep.assert_called_with(1)
+
+    @mock.patch('oauth2client.contrib.locked_file.os')
+    def test_unlock(self, os_mock):
+        instance, _ = self._make_one()
+        instance._locked = True
+        lock_fd_mock = instance._lock_fd = mock.Mock()
+        instance._fh = mock.Mock()
+
+        instance.unlock_and_close()
+
+        self.assertFalse(instance.is_locked())
+        os_mock.close.assert_called_once_with(lock_fd_mock)
+        self.assertTrue(os_mock.unlink.called)
+        self.assertTrue(instance._fh.close.called)
+
+
+class TestLockedFile(unittest2.TestCase):
+
+    @mock.patch('oauth2client.contrib.locked_file._PosixOpener')
+    def _make_one(self, opener_ctor_mock):
+        opener_mock = mock.Mock()
+        opener_ctor_mock.return_value = opener_mock
+        return locked_file.LockedFile(
+            'a_file', 'r+', 'r', use_native_locking=False), opener_mock
+
+    @mock.patch('oauth2client.contrib.locked_file._PosixOpener')
+    def test_ctor_minimal(self, opener_mock):
+        locked_file.LockedFile(
+            'a_file', 'r+', 'r', use_native_locking=False)
+        opener_mock.assert_called_with('a_file', 'r+', 'r')
+
+    @mock.patch.dict('sys.modules', {
+        'oauth2client.contrib._win32_opener': mock.Mock()})
+    def test_ctor_native_win32(self):
+        _win32_opener_mock = sys.modules['oauth2client.contrib._win32_opener']
+        locked_file.LockedFile(
+            'a_file', 'r+', 'r', use_native_locking=True)
+        _win32_opener_mock._Win32Opener.assert_called_with('a_file', 'r+', 'r')
+
+    @mock.patch.dict('sys.modules', {
+        'oauth2client.contrib._win32_opener': None,
+        'oauth2client.contrib._fcntl_opener': mock.Mock()})
+    def test_ctor_native_fcntl(self):
+        _fnctl_opener_mock = sys.modules['oauth2client.contrib._fcntl_opener']
+        locked_file.LockedFile(
+            'a_file', 'r+', 'r', use_native_locking=True)
+        _fnctl_opener_mock._FcntlOpener.assert_called_with('a_file', 'r+', 'r')
+
+    @mock.patch('oauth2client.contrib.locked_file._PosixOpener')
+    @mock.patch.dict('sys.modules', {
+        'oauth2client.contrib._win32_opener': None,
+        'oauth2client.contrib._fcntl_opener': None})
+    def test_ctor_native_posix_fallback(self, opener_mock):
+        locked_file.LockedFile(
+            'a_file', 'r+', 'r', use_native_locking=True)
+        opener_mock.assert_called_with('a_file', 'r+', 'r')
+
+    def test_filename(self):
+        instance, opener = self._make_one()
+        opener._filename = 'some file'
+        self.assertEqual(instance.filename(), 'some file')
+
+    def test_file_handle(self):
+        instance, opener = self._make_one()
+        self.assertEqual(instance.file_handle(), opener.file_handle())
+        self.assertTrue(opener.file_handle.called)
+
+    def test_is_locked(self):
+        instance, opener = self._make_one()
+        self.assertEqual(instance.is_locked(), opener.is_locked())
+        self.assertTrue(opener.is_locked.called)
+
+    def test_open_and_lock(self):
+        instance, opener = self._make_one()
+        instance.open_and_lock()
+        opener.open_and_lock.assert_called_with(0, 0.05)
+
+    def test_unlock_and_close(self):
+        instance, opener = self._make_one()
+        instance.unlock_and_close()
+        opener.unlock_and_close.assert_called_with()
diff --git a/tests/contrib/test_metadata.py b/tests/contrib/test_metadata.py
index cd48f0a..7f11d04 100644
--- a/tests/contrib/test_metadata.py
+++ b/tests/contrib/test_metadata.py
@@ -14,103 +14,84 @@
 
 import datetime
 import json
-import unittest
 
+import httplib2
 import mock
 from six.moves import http_client
+import unittest2
 
 from oauth2client.contrib import _metadata
-from tests import http_mock
-
 
 PATH = 'instance/service-accounts/default'
 DATA = {'foo': 'bar'}
 EXPECTED_URL = (
     'http://metadata.google.internal/computeMetadata/v1/instance'
     '/service-accounts/default')
+EXPECTED_KWARGS = dict(headers=_metadata.METADATA_HEADERS)
 
 
 def request_mock(status, content_type, content):
-    headers = {'status': status, 'content-type': content_type}
-    http = http_mock.HttpMock(headers=headers,
-                              data=content.encode('utf-8'))
-    return http
+    return mock.MagicMock(return_value=(
+        httplib2.Response(
+            {'status': status, 'content-type': content_type}
+        ),
+        content.encode('utf-8')
+    ))
 
 
-class TestMetadata(unittest.TestCase):
+class TestMetadata(unittest2.TestCase):
 
     def test_get_success_json(self):
-        http = request_mock(
+        http_request = request_mock(
             http_client.OK, 'application/json', json.dumps(DATA))
         self.assertEqual(
-            _metadata.get(http, PATH),
+            _metadata.get(http_request, PATH),
             DATA
         )
-
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, EXPECTED_URL)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
+        http_request.assert_called_once_with(EXPECTED_URL, **EXPECTED_KWARGS)
 
     def test_get_success_string(self):
-        http = request_mock(
+        http_request = request_mock(
             http_client.OK, 'text/html', '<p>Hello World!</p>')
         self.assertEqual(
-            _metadata.get(http, PATH),
+            _metadata.get(http_request, PATH),
             '<p>Hello World!</p>'
         )
-
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, EXPECTED_URL)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
+        http_request.assert_called_once_with(EXPECTED_URL, **EXPECTED_KWARGS)
 
     def test_get_failure(self):
-        http = request_mock(
+        http_request = request_mock(
             http_client.NOT_FOUND, 'text/html', '<p>Error</p>')
-        with self.assertRaises(http_client.HTTPException):
-            _metadata.get(http, PATH)
+        with self.assertRaises(httplib2.HttpLib2Error):
+            _metadata.get(http_request, PATH)
 
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, EXPECTED_URL)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
+        http_request.assert_called_once_with(EXPECTED_URL, **EXPECTED_KWARGS)
 
     @mock.patch(
         'oauth2client.client._UTCNOW',
         return_value=datetime.datetime.min)
     def test_get_token_success(self, now):
-        http = request_mock(
+        http_request = request_mock(
             http_client.OK,
             'application/json',
             json.dumps({'access_token': 'a', 'expires_in': 100})
         )
-        token, expiry = _metadata.get_token(http=http)
+        token, expiry = _metadata.get_token(http_request=http_request)
         self.assertEqual(token, 'a')
         self.assertEqual(
             expiry, datetime.datetime.min + datetime.timedelta(seconds=100))
-        # Verify mocks.
+        http_request.assert_called_once_with(
+            EXPECTED_URL + '/token',
+            **EXPECTED_KWARGS
+        )
         now.assert_called_once_with()
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, EXPECTED_URL + '/token')
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
 
     def test_service_account_info(self):
-        http = request_mock(
+        http_request = request_mock(
             http_client.OK, 'application/json', json.dumps(DATA))
-        info = _metadata.get_service_account_info(http)
+        info = _metadata.get_service_account_info(http_request)
         self.assertEqual(info, DATA)
-        # Verify mock.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, EXPECTED_URL + '/?recursive=True')
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertEqual(http.headers, _metadata.METADATA_HEADERS)
+        http_request.assert_called_once_with(
+            EXPECTED_URL + '/?recursive=True',
+            **EXPECTED_KWARGS
+        )
diff --git a/tests/contrib/test_multiprocess_file_storage.py b/tests/contrib/test_multiprocess_file_storage.py
index d8b91a9..bf30c14 100644
--- a/tests/contrib/test_multiprocess_file_storage.py
+++ b/tests/contrib/test_multiprocess_file_storage.py
@@ -20,16 +20,16 @@
 import multiprocessing
 import os
 import tempfile
-import unittest
 
 import fasteners
 import mock
-import six
-from six.moves import urllib_parse
+from six import StringIO
+import unittest2
 
 from oauth2client import client
 from oauth2client.contrib import multiprocess_file_storage
-from tests import http_mock
+
+from ..http_mock import HttpMockSequence
 
 
 @contextlib.contextmanager
@@ -68,10 +68,14 @@
         'access_token': new_token,
         'expires_in': '3600',
     })
-    return http_mock.HttpMock(data=token_response)
+    http = HttpMockSequence([
+        ({'status': '200'}, token_response),
+    ])
+
+    return http
 
 
-class MultiprocessStorageBehaviorTests(unittest.TestCase):
+class MultiprocessStorageBehaviorTests(unittest2.TestCase):
 
     def setUp(self):
         filehandle, self.filename = tempfile.mkstemp(
@@ -111,23 +115,6 @@
 
         self.assertIsNone(credentials)
 
-    def _verify_refresh_payload(self, http, credentials):
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, credentials.token_uri)
-        self.assertEqual(http.method, 'POST')
-        expected_body = {
-            'grant_type': ['refresh_token'],
-            'client_id': [credentials.client_id],
-            'client_secret': [credentials.client_secret],
-            'refresh_token': [credentials.refresh_token],
-        }
-        self.assertEqual(urllib_parse.parse_qs(http.body), expected_body)
-        expected_headers = {
-            'content-type': 'application/x-www-form-urlencoded',
-            'user-agent': credentials.user_agent,
-        }
-        self.assertEqual(http.headers, expected_headers)
-
     def test_single_process_refresh(self):
         store = multiprocess_file_storage.MultiprocessFileStorage(
             self.filename, 'single-process')
@@ -141,9 +128,6 @@
         retrieved = store.get()
         self.assertEqual(retrieved.access_token, 'new_token')
 
-        # Verify mocks.
-        self._verify_refresh_payload(http, credentials)
-
     def test_multi_process_refresh(self):
         # This will test that two processes attempting to refresh credentials
         # will only refresh once.
@@ -152,7 +136,6 @@
         credentials = _create_test_credentials()
         credentials.set_store(store)
         store.put(credentials)
-        actual_token = 'b'
 
         def child_process_func(
                 die_event, ready_event, check_event):  # pragma: NO COVER
@@ -173,12 +156,10 @@
 
             credentials.store.acquire_lock = replacement_acquire_lock
 
-            http = _generate_token_response_http(actual_token)
+            http = _generate_token_response_http('b')
             credentials.refresh(http)
-            self.assertEqual(credentials.access_token, actual_token)
 
-            # Verify mock http.
-            self._verify_refresh_payload(http, credentials)
+            self.assertEqual(credentials.access_token, 'b')
 
         check_event = multiprocessing.Event()
         with scoped_child_process(child_process_func, check_event=check_event):
@@ -187,17 +168,15 @@
                 store._backend._process_lock.acquire(blocking=False))
             check_event.set()
 
-            http = _generate_token_response_http('not ' + actual_token)
-            credentials.refresh(http=http)
             # The child process will refresh first, so we should end up
-            # with `actual_token`' as the token.
-            self.assertEqual(credentials.access_token, actual_token)
-
-            # Make sure the refresh did not make a request.
-            self.assertEqual(http.requests, 0)
+            # with 'b' as the token.
+            http = mock.Mock()
+            credentials.refresh(http=http)
+            self.assertEqual(credentials.access_token, 'b')
+            self.assertFalse(http.request.called)
 
         retrieved = store.get()
-        self.assertEqual(retrieved.access_token, actual_token)
+        self.assertEqual(retrieved.access_token, 'b')
 
     def test_read_only_file_fail_lock(self):
         credentials = _create_test_credentials()
@@ -221,7 +200,7 @@
         self.assertIsNotNone(store.get())
 
 
-class MultiprocessStorageUnitTests(unittest.TestCase):
+class MultiprocessStorageUnitTests(unittest2.TestCase):
 
     def setUp(self):
         filehandle, self.filename = tempfile.mkstemp(
@@ -254,7 +233,7 @@
 
     def test__read_write_credentials_file(self):
         credentials = _create_test_credentials()
-        contents = six.StringIO()
+        contents = StringIO()
 
         multiprocess_file_storage._write_credentials_file(
             contents, {'key': credentials})
@@ -274,23 +253,23 @@
         # the invalid one but still load the valid one.
         data['credentials']['invalid'] = '123'
         results = multiprocess_file_storage._load_credentials_file(
-            six.StringIO(json.dumps(data)))
+            StringIO(json.dumps(data)))
         self.assertNotIn('invalid', results)
         self.assertEqual(
             results['key'].access_token, credentials.access_token)
 
     def test__load_credentials_file_invalid_json(self):
-        contents = six.StringIO('{[')
+        contents = StringIO('{[')
         self.assertEqual(
             multiprocess_file_storage._load_credentials_file(contents), {})
 
     def test__load_credentials_file_no_file_version(self):
-        contents = six.StringIO('{}')
+        contents = StringIO('{}')
         self.assertEqual(
             multiprocess_file_storage._load_credentials_file(contents), {})
 
     def test__load_credentials_file_bad_file_version(self):
-        contents = six.StringIO(json.dumps({'file_version': 1}))
+        contents = StringIO(json.dumps({'file_version': 1}))
         self.assertEqual(
             multiprocess_file_storage._load_credentials_file(contents), {})
 
@@ -331,4 +310,4 @@
 
 
 if __name__ == '__main__':  # pragma: NO COVER
-    unittest.main()
+    unittest2.main()
diff --git a/tests/contrib/test_multistore_file.py b/tests/contrib/test_multistore_file.py
new file mode 100644
index 0000000..b5cb598
--- /dev/null
+++ b/tests/contrib/test_multistore_file.py
@@ -0,0 +1,383 @@
+# Copyright 2015 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 oauth2client.multistore_file."""
+
+import datetime
+import errno
+import os
+import stat
+import tempfile
+
+import mock
+import unittest2
+
+from oauth2client import client
+from oauth2client import util
+from oauth2client.contrib import locked_file
+from oauth2client.contrib import multistore_file
+
+_filehandle, FILENAME = tempfile.mkstemp('oauth2client_test.data')
+os.close(_filehandle)
+
+
+class _MockLockedFile(object):
+
+    def __init__(self, filename_str, error_class, error_code):
+        self.filename_str = filename_str
+        self.error_class = error_class
+        self.error_code = error_code
+        self.open_and_lock_called = False
+
+    def open_and_lock(self):
+        self.open_and_lock_called = True
+        raise self.error_class(self.error_code, '')
+
+    def is_locked(self):
+        return False
+
+    def filename(self):
+        return self.filename_str
+
+
+class Test__dict_to_tuple_key(unittest2.TestCase):
+
+    def test_key_conversions(self):
+        key1, val1 = 'somekey', 'some value'
+        key2, val2 = 'another', 'something else'
+        key3, val3 = 'onemore', 'foo'
+        test_dict = {
+            key1: val1,
+            key2: val2,
+            key3: val3,
+        }
+        tuple_key = multistore_file._dict_to_tuple_key(test_dict)
+
+        # the resulting key should be naturally sorted
+        expected_output = (
+            (key2, val2),
+            (key3, val3),
+            (key1, val1),
+        )
+        self.assertTupleEqual(expected_output, tuple_key)
+        # check we get the original dictionary back
+        self.assertDictEqual(test_dict, dict(tuple_key))
+
+
+class MultistoreFileTests(unittest2.TestCase):
+
+    def tearDown(self):
+        try:
+            os.unlink(FILENAME)
+        except OSError:
+            pass
+
+    def setUp(self):
+        try:
+            os.unlink(FILENAME)
+        except OSError:
+            pass
+
+    def _create_test_credentials(self, client_id='some_client_id',
+                                 expiration=None):
+        access_token = 'foo'
+        client_secret = 'cOuDdkfjxxnv+'
+        refresh_token = '1/0/a.df219fjls0'
+        token_expiry = expiration or datetime.datetime.utcnow()
+        token_uri = 'https://www.google.com/accounts/o8/oauth2/token'
+        user_agent = 'refresh_checker/1.0'
+
+        credentials = client.OAuth2Credentials(
+            access_token, client_id, client_secret,
+            refresh_token, token_expiry, token_uri,
+            user_agent)
+        return credentials
+
+    def test_lock_file_raises_ioerror(self):
+        filehandle, filename = tempfile.mkstemp()
+        os.close(filehandle)
+
+        try:
+            for error_code in (errno.EDEADLK, errno.ENOSYS, errno.ENOLCK,
+                               errno.EACCES):
+                for error_class in (IOError, OSError):
+                    multistore = multistore_file._MultiStore(filename)
+                    multistore._file = _MockLockedFile(
+                        filename, error_class, error_code)
+                    # Should not raise though the underlying file class did.
+                    multistore._lock()
+                    self.assertTrue(multistore._file.open_and_lock_called)
+        finally:
+            os.unlink(filename)
+
+    def test_lock_file_raise_unexpected_error(self):
+        filehandle, filename = tempfile.mkstemp()
+        os.close(filehandle)
+
+        try:
+            multistore = multistore_file._MultiStore(filename)
+            multistore._file = _MockLockedFile(filename, IOError, errno.EBUSY)
+            with self.assertRaises(IOError):
+                multistore._lock()
+            self.assertTrue(multistore._file.open_and_lock_called)
+        finally:
+            os.unlink(filename)
+
+    def test_read_only_file_fail_lock(self):
+        credentials = self._create_test_credentials()
+
+        open(FILENAME, 'a+b').close()
+        os.chmod(FILENAME, 0o400)
+
+        store = multistore_file.get_credential_storage(
+            FILENAME,
+            credentials.client_id,
+            credentials.user_agent,
+            ['some-scope', 'some-other-scope'])
+
+        store.put(credentials)
+        if os.name == 'posix':  # pragma: NO COVER
+            self.assertTrue(store._multistore._read_only)
+        os.chmod(FILENAME, 0o600)
+
+    def test_read_only_file_fail_lock_no_warning(self):
+        open(FILENAME, 'a+b').close()
+        os.chmod(FILENAME, 0o400)
+
+        multistore = multistore_file._MultiStore(FILENAME)
+
+        with mock.patch.object(multistore_file.logger, 'warn') as mock_warn:
+            multistore._warn_on_readonly = False
+            multistore._lock()
+            self.assertFalse(mock_warn.called)
+
+    def test_lock_skip_refresh(self):
+        with open(FILENAME, 'w') as f:
+            f.write('123')
+        os.chmod(FILENAME, 0o400)
+
+        multistore = multistore_file._MultiStore(FILENAME)
+
+        refresh_patch = mock.patch.object(
+            multistore, '_refresh_data_cache')
+
+        with refresh_patch as refresh_mock:
+            multistore._data = {}
+            multistore._lock()
+            self.assertFalse(refresh_mock.called)
+
+    @unittest2.skipIf(not hasattr(os, 'symlink'), 'No symlink available')
+    def test_multistore_no_symbolic_link_files(self):
+        SYMFILENAME = FILENAME + 'sym'
+        os.symlink(FILENAME, SYMFILENAME)
+        store = multistore_file.get_credential_storage(
+            SYMFILENAME,
+            'some_client_id',
+            'user-agent/1.0',
+            ['some-scope', 'some-other-scope'])
+        try:
+            with self.assertRaises(
+                    locked_file.CredentialsFileSymbolicLinkError):
+                store.get()
+        finally:
+            os.unlink(SYMFILENAME)
+
+    def test_multistore_non_existent_file(self):
+        store = multistore_file.get_credential_storage(
+            FILENAME,
+            'some_client_id',
+            'user-agent/1.0',
+            ['some-scope', 'some-other-scope'])
+
+        credentials = store.get()
+        self.assertEquals(None, credentials)
+
+    def test_multistore_file(self):
+        credentials = self._create_test_credentials()
+
+        store = multistore_file.get_credential_storage(
+            FILENAME,
+            credentials.client_id,
+            credentials.user_agent,
+            ['some-scope', 'some-other-scope'])
+
+        # Save credentials
+        store.put(credentials)
+        credentials = store.get()
+
+        self.assertNotEquals(None, credentials)
+        self.assertEquals('foo', credentials.access_token)
+
+        # Delete credentials
+        store.delete()
+        credentials = store.get()
+
+        self.assertEquals(None, credentials)
+
+        if os.name == 'posix':  # pragma: NO COVER
+            self.assertEquals(
+                0o600, stat.S_IMODE(os.stat(FILENAME).st_mode))
+
+    def test_multistore_file_custom_key(self):
+        credentials = self._create_test_credentials()
+
+        custom_key = {'myapp': 'testing', 'clientid': 'some client'}
+        store = multistore_file.get_credential_storage_custom_key(
+            FILENAME, custom_key)
+
+        store.put(credentials)
+        stored_credentials = store.get()
+
+        self.assertNotEquals(None, stored_credentials)
+        self.assertEqual(credentials.access_token,
+                         stored_credentials.access_token)
+
+        store.delete()
+        stored_credentials = store.get()
+
+        self.assertEquals(None, stored_credentials)
+
+    def test_multistore_file_custom_string_key(self):
+        credentials = self._create_test_credentials()
+
+        # store with string key
+        store = multistore_file.get_credential_storage_custom_string_key(
+            FILENAME, 'mykey')
+
+        store.put(credentials)
+        stored_credentials = store.get()
+
+        self.assertNotEquals(None, stored_credentials)
+        self.assertEqual(credentials.access_token,
+                         stored_credentials.access_token)
+
+        # try retrieving with a dictionary
+        multistore_file.get_credential_storage_custom_string_key(
+            FILENAME, {'key': 'mykey'})
+        stored_credentials = store.get()
+        self.assertNotEquals(None, stored_credentials)
+        self.assertEqual(credentials.access_token,
+                         stored_credentials.access_token)
+
+        store.delete()
+        stored_credentials = store.get()
+
+        self.assertEquals(None, stored_credentials)
+
+    def test_multistore_file_backwards_compatibility(self):
+        credentials = self._create_test_credentials()
+        scopes = ['scope1', 'scope2']
+
+        # store the credentials using the legacy key method
+        store = multistore_file.get_credential_storage(
+            FILENAME, 'client_id', 'user_agent', scopes)
+        store.put(credentials)
+
+        # retrieve the credentials using a custom key that matches the
+        # legacy key
+        key = {'clientId': 'client_id', 'userAgent': 'user_agent',
+               'scope': util.scopes_to_string(scopes)}
+        store = multistore_file.get_credential_storage_custom_key(
+            FILENAME, key)
+        stored_credentials = store.get()
+
+        self.assertEqual(credentials.access_token,
+                         stored_credentials.access_token)
+
+    def test_multistore_file_get_all_keys(self):
+        # start with no keys
+        keys = multistore_file.get_all_credential_keys(FILENAME)
+        self.assertEquals([], keys)
+
+        # store credentials
+        credentials = self._create_test_credentials(client_id='client1')
+        custom_key = {'myapp': 'testing', 'clientid': 'client1'}
+        store1 = multistore_file.get_credential_storage_custom_key(
+            FILENAME, custom_key)
+        store1.put(credentials)
+
+        keys = multistore_file.get_all_credential_keys(FILENAME)
+        self.assertEquals([custom_key], keys)
+
+        # store more credentials
+        credentials = self._create_test_credentials(client_id='client2')
+        string_key = 'string_key'
+        store2 = multistore_file.get_credential_storage_custom_string_key(
+            FILENAME, string_key)
+        store2.put(credentials)
+
+        keys = multistore_file.get_all_credential_keys(FILENAME)
+        self.assertEquals(2, len(keys))
+        self.assertTrue(custom_key in keys)
+        self.assertTrue({'key': string_key} in keys)
+
+        # back to no keys
+        store1.delete()
+        store2.delete()
+        keys = multistore_file.get_all_credential_keys(FILENAME)
+        self.assertEquals([], keys)
+
+    def _refresh_data_cache_helper(self):
+        multistore = multistore_file._MultiStore(FILENAME)
+        json_patch = mock.patch.object(multistore, '_locked_json_read')
+
+        return multistore, json_patch
+
+    def test__refresh_data_cache_bad_json(self):
+        multistore, json_patch = self._refresh_data_cache_helper()
+
+        with json_patch as json_mock:
+            json_mock.side_effect = ValueError('')
+            multistore._refresh_data_cache()
+            self.assertTrue(json_mock.called)
+            self.assertEqual(multistore._data, {})
+
+    def test__refresh_data_cache_bad_version(self):
+        multistore, json_patch = self._refresh_data_cache_helper()
+
+        with json_patch as json_mock:
+            json_mock.return_value = {}
+            multistore._refresh_data_cache()
+            self.assertTrue(json_mock.called)
+            self.assertEqual(multistore._data, {})
+
+    def test__refresh_data_cache_newer_version(self):
+        multistore, json_patch = self._refresh_data_cache_helper()
+
+        with json_patch as json_mock:
+            json_mock.return_value = {'file_version': 5}
+            with self.assertRaises(multistore_file.NewerCredentialStoreError):
+                multistore._refresh_data_cache()
+            self.assertTrue(json_mock.called)
+
+    def test__refresh_data_cache_bad_credentials(self):
+        multistore, json_patch = self._refresh_data_cache_helper()
+
+        with json_patch as json_mock:
+            json_mock.return_value = {
+                'file_version': 1,
+                'data': [
+                    {'lol': 'this is a bad credential object.'}
+                ]}
+            multistore._refresh_data_cache()
+            self.assertTrue(json_mock.called)
+            self.assertEqual(multistore._data, {})
+
+    def test__delete_credential_nonexistent(self):
+        multistore = multistore_file._MultiStore(FILENAME)
+
+        with mock.patch.object(multistore, '_write') as write_mock:
+            multistore._data = {}
+            multistore._delete_credential('nonexistent_key')
+            self.assertTrue(write_mock.called)
diff --git a/tests/contrib/test_sqlalchemy.py b/tests/contrib/test_sqlalchemy.py
index 068aa92..421f516 100644
--- a/tests/contrib/test_sqlalchemy.py
+++ b/tests/contrib/test_sqlalchemy.py
@@ -13,12 +13,11 @@
 # limitations under the License.
 
 import datetime
-import unittest
 
-import mock
 import sqlalchemy
 import sqlalchemy.ext.declarative
 import sqlalchemy.orm
+import unittest2
 
 import oauth2client
 import oauth2client.client
@@ -37,7 +36,7 @@
         oauth2client.contrib.sqlalchemy.CredentialsType)
 
 
-class TestSQLAlchemyStorage(unittest.TestCase):
+class TestSQLAlchemyStorage(unittest2.TestCase):
     def setUp(self):
         engine = sqlalchemy.create_engine('sqlite://')
         Base.metadata.create_all(engine)
@@ -67,8 +66,7 @@
         self.assertEqual(result.token_uri, self.credentials.token_uri)
         self.assertEqual(result.user_agent, self.credentials.user_agent)
 
-    @mock.patch('oauth2client.client.OAuth2Credentials.set_store')
-    def test_get(self, set_store):
+    def test_get(self):
         session = self.session()
         credentials_storage = oauth2client.contrib.sqlalchemy.Storage(
             session=session,
@@ -77,21 +75,7 @@
             key_value=1,
             property_name='credentials',
         )
-        # No credentials stored
         self.assertIsNone(credentials_storage.get())
-
-        # Invalid credentials stored
-        session.add(DummyModel(
-            key=1,
-            credentials=oauth2client.client.Credentials(),
-        ))
-        session.commit()
-        bad_credentials = credentials_storage.get()
-        self.assertIsInstance(bad_credentials, oauth2client.client.Credentials)
-        set_store.assert_not_called()
-
-        # Valid credentials stored
-        session.query(DummyModel).filter_by(key=1).delete()
         session.add(DummyModel(
             key=1,
             credentials=self.credentials,
@@ -99,20 +83,16 @@
         session.commit()
 
         self.compare_credentials(credentials_storage.get())
-        set_store.assert_called_with(credentials_storage)
 
     def test_put(self):
         session = self.session()
-        storage = oauth2client.contrib.sqlalchemy.Storage(
+        oauth2client.contrib.sqlalchemy.Storage(
             session=session,
             model_class=DummyModel,
             key_name='key',
             key_value=1,
             property_name='credentials',
-        )
-        # Store invalid credentials first to verify overwriting
-        storage.put(oauth2client.client.Credentials())
-        storage.put(self.credentials)
+        ).put(self.credentials)
         session.commit()
 
         entity = session.query(DummyModel).filter_by(key=1).first()
diff --git a/tests/contrib/test_xsrfutil.py b/tests/contrib/test_xsrfutil.py
index 3115827..64b842f 100644
--- a/tests/contrib/test_xsrfutil.py
+++ b/tests/contrib/test_xsrfutil.py
@@ -15,9 +15,9 @@
 """Tests for oauth2client.contrib.xsrfutil."""
 
 import base64
-import unittest
 
 import mock
+import unittest2
 
 from oauth2client import _helpers
 from oauth2client.contrib import xsrfutil
@@ -34,7 +34,10 @@
 TEST_EXTRA_INFO_2 = b'more_extra_info'
 
 
-class Test_generate_token(unittest.TestCase):
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+class Test_generate_token(unittest2.TestCase):
 
     def test_bad_positional(self):
         # Need 2 positional arguments.
@@ -46,10 +49,10 @@
 
     def test_it(self):
         digest = b'foobar'
-        digester = mock.Mock()
-        digester.digest = mock.Mock(name='digest', return_value=digest)
+        digester = mock.MagicMock()
+        digester.digest = mock.MagicMock(name='digest', return_value=digest)
         with mock.patch('oauth2client.contrib.xsrfutil.hmac') as hmac:
-            hmac.new = mock.Mock(name='new', return_value=digester)
+            hmac.new = mock.MagicMock(name='new', return_value=digester)
             token = xsrfutil.generate_token(TEST_KEY,
                                             TEST_USER_ID_1,
                                             action_id=TEST_ACTION_ID_1,
@@ -75,13 +78,13 @@
     def test_with_system_time(self):
         digest = b'foobar'
         curr_time = 1440449755.74
-        digester = mock.Mock()
-        digester.digest = mock.Mock(name='digest', return_value=digest)
+        digester = mock.MagicMock()
+        digester.digest = mock.MagicMock(name='digest', return_value=digest)
         with mock.patch('oauth2client.contrib.xsrfutil.hmac') as hmac:
-            hmac.new = mock.Mock(name='new', return_value=digester)
+            hmac.new = mock.MagicMock(name='new', return_value=digester)
 
             with mock.patch('oauth2client.contrib.xsrfutil.time') as time:
-                time.time = mock.Mock(name='time', return_value=curr_time)
+                time.time = mock.MagicMock(name='time', return_value=curr_time)
                 # when= is omitted
                 token = xsrfutil.generate_token(TEST_KEY,
                                                 TEST_USER_ID_1,
@@ -108,7 +111,7 @@
                 self.assertEqual(token, expected_token)
 
 
-class Test_validate_token(unittest.TestCase):
+class Test_validate_token(unittest2.TestCase):
 
     def test_bad_positional(self):
         # Need 3 positional arguments.
@@ -139,7 +142,7 @@
         key = user_id = None
         token = base64.b64encode(_helpers._to_bytes(str(token_time)))
         with mock.patch('oauth2client.contrib.xsrfutil.time') as time:
-            time.time = mock.Mock(name='time', return_value=curr_time)
+            time.time = mock.MagicMock(name='time', return_value=curr_time)
             self.assertFalse(xsrfutil.validate_token(key, token, user_id))
             time.time.assert_called_once_with()
 
@@ -215,7 +218,7 @@
                                             when=token_time)
 
 
-class XsrfUtilTests(unittest.TestCase):
+class XsrfUtilTests(unittest2.TestCase):
     """Test xsrfutil functions."""
 
     def testGenerateAndValidateToken(self):
diff --git a/tests/data/client_secrets.json b/tests/data/client_secrets.json
index 81079e6..5356103 100644
--- a/tests/data/client_secrets.json
+++ b/tests/data/client_secrets.json
@@ -4,7 +4,7 @@
     "client_secret": "foo_client_secret",
     "redirect_uris": [],
     "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
-    "token_uri": "https://oauth2.googleapis.com/token",
-    "revoke_uri": "https://oauth2.googleapis.com/revoke"
+    "token_uri": "https://www.googleapis.com/oauth2/v4/token",
+    "revoke_uri": "https://accounts.google.com/o/oauth2/revoke"
   }
 }
diff --git a/tests/data/unfilled_client_secrets.json b/tests/data/unfilled_client_secrets.json
index 8b5d55e..a85ca01 100644
--- a/tests/data/unfilled_client_secrets.json
+++ b/tests/data/unfilled_client_secrets.json
@@ -4,6 +4,6 @@
     "client_secret": "[[INSERT CLIENT SECRET HERE]]",
     "redirect_uris": [],
     "auth_uri": "https://accounts.google.com/o/oauth2/v2/auth",
-    "token_uri": "https://oauth2.googleapis.com/token"
+    "token_uri": "https://www.googleapis.com/oauth2/v4/token"
   }
 }
diff --git a/tests/data/user-key.json.enc b/tests/data/user-key.json.enc
index 5f53207..03e1bc6 100644
--- a/tests/data/user-key.json.enc
+++ b/tests/data/user-key.json.enc
Binary files differ
diff --git a/tests/http_mock.py b/tests/http_mock.py
index a29024f..6053299 100644
--- a/tests/http_mock.py
+++ b/tests/http_mock.py
@@ -12,41 +12,31 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""HTTP helpers mock functionality."""
+"""Copy of googleapiclient.http's mock functionality."""
 
+import httplib2
 
-from six.moves import http_client
-
-
-class ResponseMock(dict):
-    """Mock HTTP response"""
-
-    def __init__(self, vals=None):
-        if vals is None:
-            vals = {}
-        self.update(vals)
-        self.status = int(self.get('status', http_client.OK))
+# TODO(craigcitro): Find a cleaner way to share this code with googleapiclient.
 
 
 class HttpMock(object):
-    """Mock of HTTP object."""
+    """Mock of httplib2.Http"""
 
-    def __init__(self, headers=None, data=None):
+    def __init__(self, headers=None):
         """HttpMock constructor.
 
         Args:
             headers: dict, header to return with response
         """
         if headers is None:
-            headers = {'status': http_client.OK}
-        self.data = data
+            headers = {'status': '200'}
+        self.data = None
         self.response_headers = headers
         self.headers = None
         self.uri = None
         self.method = None
         self.body = None
         self.headers = None
-        self.requests = 0
 
     def request(self, uri,
                 method='GET',
@@ -58,24 +48,22 @@
         self.method = method
         self.body = body
         self.headers = headers
-        self.redirections = redirections
-        self.requests += 1
-        return ResponseMock(self.response_headers), self.data
+        return httplib2.Response(self.response_headers), self.data
 
 
 class HttpMockSequence(object):
-    """Mock of HTTP object with multiple return values.
+    """Mock of httplib2.Http
 
     Mocks a sequence of calls to request returning different responses for each
     call. Create an instance initialized with the desired response headers
-    and content and then use as if an HttpMock instance::
+    and content and then use as if an httplib2.Http instance::
 
         http = HttpMockSequence([
             ({'status': '401'}, b''),
             ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
             ({'status': '200'}, 'echo_request_headers'),
         ])
-        resp, content = http.request('http://examples.com')
+        resp, content = http.request("http://examples.com")
 
     There are special values you can pass in for content to trigger
     behavours that are helpful in testing.
@@ -92,6 +80,7 @@
             iterable: iterable, a sequence of pairs of (headers, body)
         """
         self._iterable = iterable
+        self.follow_redirects = True
         self.requests = []
 
     def request(self, uri,
@@ -101,12 +90,7 @@
                 redirections=1,
                 connection_type=None):
         resp, content = self._iterable.pop(0)
-        self.requests.append({
-            'method': method,
-            'uri': uri,
-            'body': body,
-            'headers': headers,
-        })
+        self.requests.append({'uri': uri, 'body': body, 'headers': headers})
         # Read any underlying stream before sending the request.
         body_stream_content = (body.read()
                                if getattr(body, 'read', None) else None)
@@ -115,7 +99,7 @@
         elif content == 'echo_request_body':
             content = (body
                        if body_stream_content is None else body_stream_content)
-        return ResponseMock(resp), content
+        return httplib2.Response(resp), content
 
 
 class CacheMock(object):
diff --git a/tests/test__helpers.py b/tests/test__helpers.py
index 00cd38a..cd54186 100644
--- a/tests/test__helpers.py
+++ b/tests/test__helpers.py
@@ -11,133 +11,14 @@
 # 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 oauth2client._helpers."""
 
-import unittest
-
-import mock
+import unittest2
 
 from oauth2client import _helpers
-from tests import test_client
 
 
-class PositionalTests(unittest.TestCase):
-
-    def test_usage(self):
-        _helpers.positional_parameters_enforcement = (
-            _helpers.POSITIONAL_EXCEPTION)
-
-        # 1 positional arg, 1 keyword-only arg.
-        @_helpers.positional(1)
-        def function(pos, kwonly=None):
-            return True
-
-        self.assertTrue(function(1))
-        self.assertTrue(function(1, kwonly=2))
-        with self.assertRaises(TypeError):
-            function(1, 2)
-
-        # No positional, but a required keyword arg.
-        @_helpers.positional(0)
-        def function2(required_kw):
-            return True
-
-        self.assertTrue(function2(required_kw=1))
-        with self.assertRaises(TypeError):
-            function2(1)
-
-        # Unspecified positional, should automatically figure out 1 positional
-        # 1 keyword-only (same as first case above).
-        @_helpers.positional
-        def function3(pos, kwonly=None):
-            return True
-
-        self.assertTrue(function3(1))
-        self.assertTrue(function3(1, kwonly=2))
-        with self.assertRaises(TypeError):
-            function3(1, 2)
-
-    @mock.patch('oauth2client._helpers.logger')
-    def test_enforcement_warning(self, mock_logger):
-        _helpers.positional_parameters_enforcement = (
-            _helpers.POSITIONAL_WARNING)
-
-        @_helpers.positional(1)
-        def function(pos, kwonly=None):
-            return True
-
-        self.assertTrue(function(1, 2))
-        self.assertTrue(mock_logger.warning.called)
-
-    @mock.patch('oauth2client._helpers.logger')
-    def test_enforcement_ignore(self, mock_logger):
-        _helpers.positional_parameters_enforcement = _helpers.POSITIONAL_IGNORE
-
-        @_helpers.positional(1)
-        def function(pos, kwonly=None):
-            return True
-
-        self.assertTrue(function(1, 2))
-        self.assertFalse(mock_logger.warning.called)
-
-
-class ScopeToStringTests(unittest.TestCase):
-
-    def test_iterables(self):
-        cases = [
-            ('', ''),
-            ('', ()),
-            ('', []),
-            ('', ('',)),
-            ('', ['', ]),
-            ('a', ('a',)),
-            ('b', ['b', ]),
-            ('a b', ['a', 'b']),
-            ('a b', ('a', 'b')),
-            ('a b', 'a b'),
-            ('a b', (s for s in ['a', 'b'])),
-        ]
-        for expected, case in cases:
-            self.assertEqual(expected, _helpers.scopes_to_string(case))
-
-
-class StringToScopeTests(unittest.TestCase):
-
-    def test_conversion(self):
-        cases = [
-            (['a', 'b'], ['a', 'b']),
-            ('', []),
-            ('a', ['a']),
-            ('a b c d e f', ['a', 'b', 'c', 'd', 'e', 'f']),
-        ]
-
-        for case, expected in cases:
-            self.assertEqual(expected, _helpers.string_to_scopes(case))
-
-
-class AddQueryParameterTests(unittest.TestCase):
-
-    def test__add_query_parameter(self):
-        self.assertEqual(
-            _helpers._add_query_parameter('/action', 'a', None),
-            '/action')
-        self.assertEqual(
-            _helpers._add_query_parameter('/action', 'a', 'b'),
-            '/action?a=b')
-        self.assertEqual(
-            _helpers._add_query_parameter('/action?a=b', 'a', 'c'),
-            '/action?a=c')
-        # Order is non-deterministic.
-        self.assertIn(
-            _helpers._add_query_parameter('/action?a=b', 'c', 'd'),
-            ['/action?a=b&c=d', '/action?c=d&a=b'])
-        self.assertEqual(
-            _helpers._add_query_parameter('/action', 'a', ' ='),
-            '/action?a=+%3D')
-
-
-class Test__parse_pem_key(unittest.TestCase):
+class Test__parse_pem_key(unittest2.TestCase):
 
     def test_valid_input(self):
         test_string = b'1234-----BEGIN FOO BAR BAZ'
@@ -150,7 +31,7 @@
         self.assertEqual(result, None)
 
 
-class Test__json_encode(unittest.TestCase):
+class Test__json_encode(unittest2.TestCase):
 
     def test_dictionary_input(self):
         # Use only a single key since dictionary hash order
@@ -165,7 +46,7 @@
         self.assertEqual(result, '[42,1337]')
 
 
-class Test__to_bytes(unittest.TestCase):
+class Test__to_bytes(unittest2.TestCase):
 
     def test_with_bytes(self):
         value = b'bytes-val'
@@ -182,7 +63,7 @@
             _helpers._to_bytes(value)
 
 
-class Test__from_bytes(unittest.TestCase):
+class Test__from_bytes(unittest2.TestCase):
 
     def test_with_unicode(self):
         value = u'bytes-val'
@@ -199,15 +80,10 @@
             _helpers._from_bytes(value)
 
 
-class Test__urlsafe_b64encode(unittest.TestCase):
+class Test__urlsafe_b64encode(unittest2.TestCase):
 
     DEADBEEF_ENCODED = b'ZGVhZGJlZWY'
 
-    def test_valid_input_str(self):
-        test_string = 'deadbeef'
-        result = _helpers._urlsafe_b64encode(test_string)
-        self.assertEqual(result, self.DEADBEEF_ENCODED)
-
     def test_valid_input_bytes(self):
         test_string = b'deadbeef'
         result = _helpers._urlsafe_b64encode(test_string)
@@ -219,66 +95,20 @@
         self.assertEqual(result, self.DEADBEEF_ENCODED)
 
 
-class Test__urlsafe_b64decode(unittest.TestCase):
-
-    DEADBEEF_DECODED = b'deadbeef'
-
-    def test_valid_input_str(self):
-        test_string = 'ZGVhZGJlZWY'
-        result = _helpers._urlsafe_b64decode(test_string)
-        self.assertEqual(result, self.DEADBEEF_DECODED)
+class Test__urlsafe_b64decode(unittest2.TestCase):
 
     def test_valid_input_bytes(self):
         test_string = b'ZGVhZGJlZWY'
         result = _helpers._urlsafe_b64decode(test_string)
-        self.assertEqual(result, self.DEADBEEF_DECODED)
+        self.assertEqual(result, b'deadbeef')
 
     def test_valid_input_unicode(self):
-        test_string = u'ZGVhZGJlZWY'
+        test_string = b'ZGVhZGJlZWY'
         result = _helpers._urlsafe_b64decode(test_string)
-        self.assertEqual(result, self.DEADBEEF_DECODED)
+        self.assertEqual(result, b'deadbeef')
 
     def test_bad_input(self):
         import binascii
         bad_string = b'+'
         with self.assertRaises((TypeError, binascii.Error)):
             _helpers._urlsafe_b64decode(bad_string)
-
-
-class Test_update_query_params(unittest.TestCase):
-
-    def test_update_query_params_no_params(self):
-        uri = 'http://www.google.com'
-        updated = _helpers.update_query_params(uri, {'a': 'b'})
-        self.assertEqual(updated, uri + '?a=b')
-
-    def test_update_query_params_existing_params(self):
-        uri = 'http://www.google.com?x=y'
-        updated = _helpers.update_query_params(uri, {'a': 'b', 'c': 'd&'})
-        hardcoded_update = uri + '&a=b&c=d%26'
-        test_client.assertUrisEqual(self, updated, hardcoded_update)
-
-    def test_update_query_params_replace_param(self):
-        base_uri = 'http://www.google.com'
-        uri = base_uri + '?x=a'
-        updated = _helpers.update_query_params(uri, {'x': 'b', 'y': 'c'})
-        hardcoded_update = base_uri + '?x=b&y=c'
-        test_client.assertUrisEqual(self, updated, hardcoded_update)
-
-    def test_update_query_params_repeated_params(self):
-        uri = 'http://www.google.com?x=a&x=b'
-        with self.assertRaises(ValueError):
-            _helpers.update_query_params(uri, {'a': 'c'})
-
-
-class Test_parse_unique_urlencoded(unittest.TestCase):
-
-    def test_without_repeats(self):
-        content = 'a=b&c=d'
-        result = _helpers.parse_unique_urlencoded(content)
-        self.assertEqual(result, {'a': 'b', 'c': 'd'})
-
-    def test_with_repeats(self):
-        content = 'a=b&a=d'
-        with self.assertRaises(ValueError):
-            _helpers.parse_unique_urlencoded(content)
diff --git a/tests/test__pkce.py b/tests/test__pkce.py
deleted file mode 100644
index 9f66560..0000000
--- a/tests/test__pkce.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2016 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.
-
-import unittest
-
-import mock
-
-from oauth2client import _pkce
-
-
-class PKCETests(unittest.TestCase):
-
-    @mock.patch('oauth2client._pkce.os.urandom')
-    def test_verifier(self, fake_urandom):
-        canned_randomness = (
-            b'\x98\x10D7\xf3\xb7\xaa\xfc\xdd\xd3M\xe2'
-            b'\xa3,\x06\xa0\xb0\xa9\xb4\x8f\xcb\xd0'
-            b'\xf5\x86N2p\x8c]!W\x9a\xed54\x99\x9d'
-            b'\x8dv\\\xa7/\x81\xf3J\x98\xc3\x90\xee'
-            b'\xb0\x8c\xb7Zc#\x05M0O\x08\xda\t\x1f\x07'
-        )
-        fake_urandom.return_value = canned_randomness
-        expected = (
-            b'mBBEN_O3qvzd003ioywGoLCptI_L0PWGTjJwjF0hV5rt'
-            b'NTSZnY12XKcvgfNKmMOQ7rCMt1pjIwVNME8I2gkfBw'
-        )
-        result = _pkce.code_verifier()
-        self.assertEqual(result, expected)
-
-    def test_verifier_too_long(self):
-        with self.assertRaises(ValueError) as caught:
-            _pkce.code_verifier(97)
-        self.assertIn("too long", str(caught.exception))
-
-    def test_verifier_too_short(self):
-        with self.assertRaises(ValueError) as caught:
-            _pkce.code_verifier(30)
-        self.assertIn("too short", str(caught.exception))
-
-    def test_challenge(self):
-        result = _pkce.code_challenge(b'SOME_VERIFIER')
-        expected = b'6xJCQsjTtS3zjUwd8_ZqH0SyviGHnp5PsHXWKOCqDuI'
-        self.assertEqual(result, expected)
diff --git a/tests/test__pure_python_crypt.py b/tests/test__pure_python_crypt.py
index e9844b9..3c2962a 100644
--- a/tests/test__pure_python_crypt.py
+++ b/tests/test__pure_python_crypt.py
@@ -15,19 +15,19 @@
 """Unit tests for oauth2client._pure_python_crypt."""
 
 import os
-import unittest
 
 import mock
 from pyasn1_modules import pem
 import rsa
 import six
+import unittest2
 
 from oauth2client import _helpers
 from oauth2client import _pure_python_crypt
 from oauth2client import crypt
 
 
-class TestRsaVerifier(unittest.TestCase):
+class TestRsaVerifier(unittest2.TestCase):
 
     PUBLIC_KEY_FILENAME = os.path.join(os.path.dirname(__file__),
                                        'data', 'privatekey.pub')
@@ -112,7 +112,7 @@
             load_pem.assert_called_once_with(cert_bytes, 'CERTIFICATE')
 
 
-class TestRsaSigner(unittest.TestCase):
+class TestRsaSigner(unittest2.TestCase):
 
     PKCS1_KEY_FILENAME = os.path.join(os.path.dirname(__file__),
                                       'data', 'privatekey.pem')
diff --git a/tests/test__pycrypto_crypt.py b/tests/test__pycrypto_crypt.py
index 2ca18ec..2f45291 100644
--- a/tests/test__pycrypto_crypt.py
+++ b/tests/test__pycrypto_crypt.py
@@ -14,12 +14,13 @@
 """Unit tests for oauth2client._pycrypto_crypt."""
 
 import os
-import unittest
+
+import unittest2
 
 from oauth2client import crypt
 
 
-class TestPyCryptoVerifier(unittest.TestCase):
+class TestPyCryptoVerifier(unittest2.TestCase):
 
     PUBLIC_CERT_FILENAME = os.path.join(os.path.dirname(__file__),
                                         'data', 'public_cert.pem')
@@ -64,7 +65,7 @@
         self.assertIsInstance(verifier, crypt.PyCryptoVerifier)
 
 
-class TestPyCryptoSigner(unittest.TestCase):
+class TestPyCryptoSigner(unittest2.TestCase):
 
     def test_from_string_bad_key(self):
         key_bytes = 'definitely-not-pem-format'
diff --git a/tests/test_client.py b/tests/test_client.py
index 18b7df4..db75603 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -12,7 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Unit tests for oauth2client.client."""
+"""Oauth2client tests
+
+Unit tests for oauth2client.
+"""
 
 import base64
 import contextlib
@@ -23,25 +26,31 @@
 import socket
 import sys
 import tempfile
-import unittest
 
+import httplib2
 import mock
 import six
 from six.moves import http_client
 from six.moves import urllib
+import unittest2
 
 import oauth2client
 from oauth2client import _helpers
 from oauth2client import client
 from oauth2client import clientsecrets
 from oauth2client import service_account
-from oauth2client import transport
-from tests import http_mock
+from oauth2client import util
+from .http_mock import CacheMock
+from .http_mock import HttpMock
+from .http_mock import HttpMockSequence
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
 DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
 
 
+# TODO(craigcitro): This is duplicated from
+# googleapiclient.test_discovery; consolidate these definitions.
 def assertUrisEqual(testcase, expected, actual):
     """Test that URIs are the same, up to reordering of query parameters."""
     expected = urllib.parse.urlparse(expected)
@@ -68,7 +77,7 @@
     cache_mock.cache[fakename] = {client_type: client_info}
 
 
-class CredentialsTests(unittest.TestCase):
+class CredentialsTests(unittest2.TestCase):
 
     def test_to_from_json(self):
         credentials = client.Credentials()
@@ -212,7 +221,7 @@
         self.assertEqual(credentials.__dict__, {})
 
 
-class TestStorage(unittest.TestCase):
+class TestStorage(unittest2.TestCase):
 
     def test_locked_get_abstract(self):
         storage = client.Storage()
@@ -247,7 +256,7 @@
             del sys.modules[entry]
 
 
-class GoogleCredentialsTests(unittest.TestCase):
+class GoogleCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
         self.os_name = os.name
@@ -355,41 +364,67 @@
             # is cached.
             self.assertTrue(client._in_gae_environment())
 
-    def _environment_check_gce_helper(self, status_ok=True,
+    def _environment_check_gce_helper(self, status_ok=True, socket_error=False,
                                       server_software=''):
+        response = mock.MagicMock()
         if status_ok:
-            headers = {'status': http_client.OK}
-            headers.update(client._GCE_HEADERS)
+            response.status = http_client.OK
+            response.getheader = mock.MagicMock(
+                name='getheader',
+                return_value=client._DESIRED_METADATA_FLAVOR)
         else:
-            headers = {'status': http_client.NOT_FOUND}
+            response.status = http_client.NOT_FOUND
 
-        http = http_mock.HttpMock(headers=headers)
+        connection = mock.MagicMock()
+        connection.getresponse = mock.MagicMock(name='getresponse',
+                                                return_value=response)
+        if socket_error:
+            connection.getresponse.side_effect = socket.error()
+
         with mock.patch('oauth2client.client.os') as os_module:
             os_module.environ = {client._SERVER_SOFTWARE: server_software}
-            with mock.patch('oauth2client.transport.get_http_object',
-                            return_value=http) as new_http:
+            with mock.patch('oauth2client.client.six') as six_module:
+                http_client_module = six_module.moves.http_client
+                http_client_module.HTTPConnection = mock.MagicMock(
+                    name='HTTPConnection', return_value=connection)
+
                 if server_software == '':
                     self.assertFalse(client._in_gae_environment())
                 else:
                     self.assertTrue(client._in_gae_environment())
 
-                if status_ok and server_software == '':
+                if status_ok and not socket_error and server_software == '':
                     self.assertTrue(client._in_gce_environment())
                 else:
                     self.assertFalse(client._in_gce_environment())
 
-                # Verify mocks.
                 if server_software == '':
-                    new_http.assert_called_once_with(
+                    http_client_module.HTTPConnection.assert_called_once_with(
+                        client._GCE_METADATA_HOST,
                         timeout=client.GCE_METADATA_TIMEOUT)
-                    self.assertEqual(http.requests, 1)
-                    self.assertEqual(http.uri, client._GCE_METADATA_URI)
-                    self.assertEqual(http.method, 'GET')
-                    self.assertIsNone(http.body)
-                    self.assertEqual(http.headers, client._GCE_HEADERS)
+                    connection.getresponse.assert_called_once_with()
+                    # Remaining calls are not "getresponse"
+                    headers = {
+                        client._METADATA_FLAVOR_HEADER: (
+                            client._DESIRED_METADATA_FLAVOR),
+                    }
+                    self.assertEqual(connection.method_calls, [
+                        mock.call.request('GET', '/',
+                                          headers=headers),
+                        mock.call.close(),
+                    ])
+                    self.assertEqual(response.method_calls, [])
+                    if status_ok and not socket_error:
+                        response.getheader.assert_called_once_with(
+                            client._METADATA_FLAVOR_HEADER)
                 else:
-                    new_http.assert_not_called()
-                    self.assertEqual(http.requests, 0)
+                    self.assertEqual(
+                        http_client_module.HTTPConnection.mock_calls, [])
+                    self.assertEqual(connection.getresponse.mock_calls, [])
+                    # Remaining calls are not "getresponse"
+                    self.assertEqual(connection.method_calls, [])
+                    self.assertEqual(response.method_calls, [])
+                    self.assertEqual(response.getheader.mock_calls, [])
 
     def test_environment_check_gce_production(self):
         self._environment_check_gce_helper(status_ok=True)
@@ -398,21 +433,8 @@
         with mock_module_import('google.appengine'):
             self._environment_check_gce_helper(status_ok=True)
 
-    @mock.patch('oauth2client.client.os.environ',
-                new={client._SERVER_SOFTWARE: ''})
-    @mock.patch('oauth2client.transport.get_http_object',
-                return_value=object())
-    @mock.patch('oauth2client.transport.request',
-                side_effect=socket.timeout())
-    def test_environment_check_gce_timeout(self, mock_request, new_http):
-        self.assertFalse(client._in_gae_environment())
-        self.assertFalse(client._in_gce_environment())
-
-        # Verify mocks.
-        new_http.assert_called_once_with(timeout=client.GCE_METADATA_TIMEOUT)
-        mock_request.assert_called_once_with(
-            new_http.return_value, client._GCE_METADATA_URI,
-            headers=client._GCE_HEADERS)
+    def test_environment_check_gce_timeout(self):
+        self._environment_check_gce_helper(socket_error=True)
 
     def test_environ_check_gae_module_unknown(self):
         with mock_module_import('google.appengine'):
@@ -687,9 +709,8 @@
         # Make sure the well-known file actually doesn't exist.
         self.assertTrue(os.path.exists(get_well_known.return_value))
 
-        method_name = (
-            'oauth2client.client.'
-            '_get_application_default_credential_from_file')
+        method_name = \
+            'oauth2client.client._get_application_default_credential_from_file'
         result_creds = object()
         with mock.patch(method_name,
                         return_value=result_creds) as get_from_file:
@@ -819,8 +840,8 @@
         self.delete_called = True
 
 
-def _token_revoke_test_helper(testcase, revoke_raise, valid_bool_value,
-                              token_attr, http_mock):
+def _token_revoke_test_helper(testcase, status, revoke_raise,
+                              valid_bool_value, token_attr):
     current_store = getattr(testcase.credentials, 'store', None)
 
     dummy_store = DummyDeleteStorage()
@@ -829,16 +850,17 @@
     actual_do_revoke = testcase.credentials._do_revoke
     testcase.token_from_revoke = None
 
-    def do_revoke_stub(http, token):
+    def do_revoke_stub(http_request, token):
         testcase.token_from_revoke = token
-        return actual_do_revoke(http, token)
+        return actual_do_revoke(http_request, token)
     testcase.credentials._do_revoke = do_revoke_stub
 
+    http = HttpMock(headers={'status': status})
     if revoke_raise:
         testcase.assertRaises(client.TokenRevokeError,
-                              testcase.credentials.revoke, http_mock)
+                              testcase.credentials.revoke, http)
     else:
-        testcase.credentials.revoke(http_mock)
+        testcase.credentials.revoke(http)
 
     testcase.assertEqual(getattr(testcase.credentials, token_attr),
                          testcase.token_from_revoke)
@@ -848,7 +870,7 @@
     testcase.credentials.set_store(current_store)
 
 
-class BasicCredentialsTests(unittest.TestCase):
+class BasicCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
         access_token = 'foo'
@@ -863,50 +885,54 @@
             user_agent, revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
             scopes='foo', token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI)
 
-        # Provoke a failure if @_helpers.positional is not respected.
+        # Provoke a failure if @util.positional is not respected.
         self.old_positional_enforcement = (
-            _helpers.positional_parameters_enforcement)
-        _helpers.positional_parameters_enforcement = (
-            _helpers.POSITIONAL_EXCEPTION)
+            util.positional_parameters_enforcement)
+        util.positional_parameters_enforcement = (
+            util.POSITIONAL_EXCEPTION)
 
     def tearDown(self):
-        _helpers.positional_parameters_enforcement = (
+        util.positional_parameters_enforcement = (
             self.old_positional_enforcement)
 
     def test_token_refresh_success(self):
         for status_code in client.REFRESH_STATUS_CODES:
             token_response = {'access_token': '1/3w', 'expires_in': 3600}
-            json_resp = json.dumps(token_response).encode('utf-8')
-            http = http_mock.HttpMockSequence([
+            http = HttpMockSequence([
                 ({'status': status_code}, b''),
-                ({'status': http_client.OK}, json_resp),
-                ({'status': http_client.OK}, 'echo_request_headers'),
+                ({'status': '200'}, json.dumps(token_response).encode(
+                    'utf-8')),
+                ({'status': '200'}, 'echo_request_headers'),
             ])
             http = self.credentials.authorize(http)
-            resp, content = transport.request(http, 'http://example.com')
+            resp, content = http.request('http://example.com')
             self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
             self.assertFalse(self.credentials.access_token_expired)
             self.assertEqual(token_response, self.credentials.token_response)
 
     def test_recursive_authorize(self):
-        # Tests that OAuth2Credentials doesn't introduce new method
-        # constraints. Formerly, OAuth2Credentials.authorize monkeypatched the
-        # request method of the passed in HTTP object with a wrapper annotated
-        # with @_helpers.positional(1). Since the original method has no such
-        # annotation, that meant that the wrapper was violating the contract of
-        # the original method by adding a new requirement to it. And in fact
-        # the wrapper itself doesn't even respect that requirement. So before
-        # the removal of the annotation, this test would fail.
+        """Tests that OAuth2Credentials doesn't intro. new method constraints.
+
+        Formerly, OAuth2Credentials.authorize monkeypatched the request method
+        of its httplib2.Http argument with a wrapper annotated with
+        @util.positional(1). Since the original method has no such annotation,
+        that meant that the wrapper was violating the contract of the original
+        method by adding a new requirement to it. And in fact the wrapper
+        itself doesn't even respect that requirement. So before the removal of
+        the annotation, this test would fail.
+        """
         token_response = {'access_token': '1/3w', 'expires_in': 3600}
         encoded_response = json.dumps(token_response).encode('utf-8')
-        http = http_mock.HttpMock(data=encoded_response)
+        http = HttpMockSequence([
+            ({'status': '200'}, encoded_response),
+        ])
         http = self.credentials.authorize(http)
         http = self.credentials.authorize(http)
-        transport.request(http, 'http://example.com')
+        http.request('http://example.com')
 
     def test_token_refresh_failure(self):
         for status_code in client.REFRESH_STATUS_CODES:
-            http = http_mock.HttpMockSequence([
+            http = HttpMockSequence([
                 ({'status': status_code}, b''),
                 ({'status': http_client.BAD_REQUEST},
                  b'{"error":"access_denied"}'),
@@ -914,51 +940,36 @@
             http = self.credentials.authorize(http)
             with self.assertRaises(
                     client.HttpAccessTokenRefreshError) as exc_manager:
-                transport.request(http, 'http://example.com')
+                http.request('http://example.com')
             self.assertEqual(http_client.BAD_REQUEST,
                              exc_manager.exception.status)
             self.assertTrue(self.credentials.access_token_expired)
             self.assertEqual(None, self.credentials.token_response)
 
     def test_token_revoke_success(self):
-        http = http_mock.HttpMock(headers={'status': http_client.OK})
         _token_revoke_test_helper(
-            self, revoke_raise=False, valid_bool_value=True,
-            token_attr='refresh_token', http_mock=http)
+            self, '200', revoke_raise=False,
+            valid_bool_value=True, token_attr='refresh_token')
 
     def test_token_revoke_failure(self):
-        http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST})
         _token_revoke_test_helper(
-            self, revoke_raise=True, valid_bool_value=False,
-            token_attr='refresh_token', http_mock=http)
+            self, '400', revoke_raise=True,
+            valid_bool_value=False, token_attr='refresh_token')
 
     def test_token_revoke_fallback(self):
         original_credentials = self.credentials.to_json()
         self.credentials.refresh_token = None
-
-        http = http_mock.HttpMock(headers={'status': http_client.OK})
         _token_revoke_test_helper(
-            self, revoke_raise=False, valid_bool_value=True,
-            token_attr='access_token', http_mock=http)
-        self.credentials = self.credentials.from_json(original_credentials)
-
-    def test_token_revoke_405(self):
-        original_credentials = self.credentials.to_json()
-        self.credentials.refresh_token = None
-
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.METHOD_NOT_ALLOWED}, b''),
-            ({'status': http_client.OK}, b''),
-        ])
-        _token_revoke_test_helper(
-            self, revoke_raise=False, valid_bool_value=True,
-            token_attr='access_token', http_mock=http)
+            self, '200', revoke_raise=False,
+            valid_bool_value=True, token_attr='access_token')
         self.credentials = self.credentials.from_json(original_credentials)
 
     def test_non_401_error_response(self):
-        http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST})
+        http = HttpMockSequence([
+            ({'status': '400'}, b''),
+        ])
         http = self.credentials.authorize(http)
-        resp, content = transport.request(http, 'http://example.com')
+        resp, content = http.request('http://example.com')
         self.assertEqual(http_client.BAD_REQUEST, resp.status)
         self.assertEqual(None, self.credentials.token_response)
 
@@ -999,11 +1010,10 @@
         # First, test that we correctly encode basic objects, making sure
         # to include a bytes object. Note that oauth2client will normalize
         # everything to bytes, no matter what python version we're in.
-        http = credentials.authorize(http_mock.HttpMock())
+        http = credentials.authorize(HttpMock())
         headers = {u'foo': 3, b'bar': True, 'baz': b'abc'}
         cleaned_headers = {b'foo': b'3', b'bar': b'True', b'baz': b'abc'}
-        transport.request(
-            http, u'http://example.com', method=u'GET', headers=headers)
+        http.request(u'http://example.com', method=u'GET', headers=headers)
         for k, v in cleaned_headers.items():
             self.assertTrue(k in http.headers)
             self.assertEqual(v, http.headers[k])
@@ -1011,9 +1021,8 @@
         # Next, test that we do fail on unicode.
         unicode_str = six.unichr(40960) + 'abcd'
         with self.assertRaises(client.NonAsciiHeaderError):
-            transport.request(
-                http, u'http://example.com', method=u'GET',
-                headers={u'foo': unicode_str})
+            http.request(u'http://example.com', method=u'GET',
+                         headers={u'foo': unicode_str})
 
     def test_no_unicode_in_request_params(self):
         access_token = u'foo'
@@ -1028,11 +1037,10 @@
             access_token, client_id, client_secret, refresh_token,
             token_expiry, token_uri, user_agent, revoke_uri=revoke_uri)
 
-        http = http_mock.HttpMock()
+        http = HttpMock()
         http = credentials.authorize(http)
-        transport.request(
-            http, u'http://example.com', method=u'GET',
-            headers={u'foo': u'bar'})
+        http.request(u'http://example.com', method=u'GET',
+                     headers={u'foo': u'bar'})
         for k, v in six.iteritems(http.headers):
             self.assertIsInstance(k, six.binary_type)
             self.assertIsInstance(v, six.binary_type)
@@ -1040,8 +1048,8 @@
         # Test again with unicode strings that can't simply be converted
         # to ASCII.
         with self.assertRaises(client.NonAsciiHeaderError):
-            transport.request(
-                http, u'http://example.com', method=u'GET',
+            http.request(
+                u'http://example.com', method=u'GET',
                 headers={u'foo': u'\N{COMET}'})
 
         self.credentials.token_response = 'foobar'
@@ -1099,7 +1107,7 @@
             'access_token': token2,
             'expires_in': lifetime,
         }
-        http = http_mock.HttpMockSequence([
+        http = HttpMockSequence([
             ({'status': '200'}, json.dumps(token_response_first).encode(
                 'utf-8')),
             ({'status': '200'}, json.dumps(token_response_second).encode(
@@ -1173,12 +1181,11 @@
         # Specify a token so we can use it in the response.
         credentials.access_token = 'ya29-s3kr3t'
 
-        with mock.patch('oauth2client.transport.get_http_object',
-                        return_value=object()) as new_http:
+        with mock.patch('httplib2.Http',
+                        return_value=object) as http_kls:
             token_info = credentials.get_access_token()
             expires_in.assert_called_once_with()
-            refresh_mock.assert_called_once_with(new_http.return_value)
-            new_http.assert_called_once_with()
+            refresh_mock.assert_called_once_with(http_kls.return_value)
 
         self.assertIsInstance(token_info, client.AccessTokenInfo)
         self.assertEqual(token_info.access_token,
@@ -1218,25 +1225,21 @@
     def _do_refresh_request_test_helper(self, response, content,
                                         error_msg, logger, gen_body,
                                         gen_headers, store=None):
-        token_uri = 'http://token_uri'
         credentials = client.OAuth2Credentials(None, None, None, None,
-                                               None, token_uri, None)
+                                               None, None, None)
         credentials.store = store
-        http = http_mock.HttpMock(headers=response, data=content)
+        http_request = mock.Mock()
+        http_request.return_value = response, content
 
         with self.assertRaises(
                 client.HttpAccessTokenRefreshError) as exc_manager:
-            credentials._do_refresh_request(http)
+            credentials._do_refresh_request(http_request)
 
         self.assertEqual(exc_manager.exception.args, (error_msg,))
         self.assertEqual(exc_manager.exception.status, response.status)
-
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, token_uri)
-        self.assertEqual(http.method, 'POST')
-        self.assertEqual(http.body, gen_body.return_value)
-        self.assertEqual(http.headers, gen_headers.return_value)
+        http_request.assert_called_once_with(None, body=gen_body.return_value,
+                                             headers=gen_headers.return_value,
+                                             method='POST')
 
         call1 = mock.call('Refreshing access_token')
         failure_template = 'Failed to retrieve access token: %s'
@@ -1246,35 +1249,43 @@
             store.locked_put.assert_called_once_with(credentials)
 
     def test__do_refresh_request_non_json_failure(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST})
+        response = httplib2.Response({
+            'status': int(http_client.BAD_REQUEST),
+        })
         content = u'Bad request'
         error_msg = 'Invalid response {0}.'.format(int(response.status))
         self._do_refresh_request_test_helper(response, content, error_msg)
 
     def test__do_refresh_request_basic_failure(self):
-        response = http_mock.ResponseMock(
-            {'status': http_client.INTERNAL_SERVER_ERROR})
+        response = httplib2.Response({
+            'status': int(http_client.INTERNAL_SERVER_ERROR),
+        })
         content = u'{}'
         error_msg = 'Invalid response {0}.'.format(int(response.status))
         self._do_refresh_request_test_helper(response, content, error_msg)
 
     def test__do_refresh_request_failure_w_json_error(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY})
+        response = httplib2.Response({
+            'status': http_client.BAD_GATEWAY,
+        })
         error_msg = 'Hi I am an error not a bearer'
         content = json.dumps({'error': error_msg})
         self._do_refresh_request_test_helper(response, content, error_msg)
 
     def test__do_refresh_request_failure_w_json_error_and_store(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY})
+        response = httplib2.Response({
+            'status': http_client.BAD_GATEWAY,
+        })
         error_msg = 'Where are we going wearer?'
         content = json.dumps({'error': error_msg})
-        store = mock.Mock()
+        store = mock.MagicMock()
         self._do_refresh_request_test_helper(response, content, error_msg,
                                              store=store)
 
     def test__do_refresh_request_failure_w_json_error_and_desc(self):
-        response = http_mock.ResponseMock(
-            {'status': http_client.SERVICE_UNAVAILABLE})
+        response = httplib2.Response({
+            'status': http_client.SERVICE_UNAVAILABLE,
+        })
         base_error = 'Ruckus'
         error_desc = 'Can you describe the ruckus'
         content = json.dumps({
@@ -1291,20 +1302,20 @@
             None, None, None, None, None, None, None,
             revoke_uri=oauth2client.GOOGLE_REVOKE_URI)
         credentials.store = store
-
-        http = http_mock.HttpMock(headers=response, data=content)
+        http_request = mock.Mock()
+        http_request.return_value = response, content
         token = u's3kr3tz'
 
         if response.status == http_client.OK:
             self.assertFalse(credentials.invalid)
-            self.assertIsNone(credentials._do_revoke(http, token))
+            self.assertIsNone(credentials._do_revoke(http_request, token))
             self.assertTrue(credentials.invalid)
             if store is not None:
                 store.delete.assert_called_once_with()
         else:
             self.assertFalse(credentials.invalid)
             with self.assertRaises(client.TokenRevokeError) as exc_manager:
-                credentials._do_revoke(http, token)
+                credentials._do_revoke(http_request, token)
             # Make sure invalid was not flipped on.
             self.assertFalse(credentials.invalid)
             self.assertEqual(exc_manager.exception.args, (error_msg,))
@@ -1312,49 +1323,54 @@
                 store.delete.assert_not_called()
 
         revoke_uri = oauth2client.GOOGLE_REVOKE_URI + '?token=' + token
-
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, revoke_uri)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertIsNone(http.headers)
+        http_request.assert_called_once_with(revoke_uri)
 
         logger.info.assert_called_once_with('Revoking token')
 
     def test__do_revoke_success(self):
-        response = http_mock.ResponseMock()
+        response = httplib2.Response({
+            'status': http_client.OK,
+        })
         self._do_revoke_test_helper(response, b'', None)
 
     def test__do_revoke_success_with_store(self):
-        response = http_mock.ResponseMock()
-        store = mock.Mock()
+        response = httplib2.Response({
+            'status': http_client.OK,
+        })
+        store = mock.MagicMock()
         self._do_revoke_test_helper(response, b'', None, store=store)
 
     def test__do_revoke_non_json_failure(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST})
+        response = httplib2.Response({
+            'status': http_client.BAD_REQUEST,
+        })
         content = u'Bad request'
         error_msg = 'Invalid response {0}.'.format(response.status)
         self._do_revoke_test_helper(response, content, error_msg)
 
     def test__do_revoke_basic_failure(self):
-        response = http_mock.ResponseMock(
-            {'status': http_client.INTERNAL_SERVER_ERROR})
+        response = httplib2.Response({
+            'status': http_client.INTERNAL_SERVER_ERROR,
+        })
         content = u'{}'
         error_msg = 'Invalid response {0}.'.format(response.status)
         self._do_revoke_test_helper(response, content, error_msg)
 
     def test__do_revoke_failure_w_json_error(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY})
+        response = httplib2.Response({
+            'status': http_client.BAD_GATEWAY,
+        })
         error_msg = 'Hi I am an error not a bearer'
         content = json.dumps({'error': error_msg})
         self._do_revoke_test_helper(response, content, error_msg)
 
     def test__do_revoke_failure_w_json_error_and_store(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY})
+        response = httplib2.Response({
+            'status': http_client.BAD_GATEWAY,
+        })
         error_msg = 'Where are we going wearer?'
         content = json.dumps({'error': error_msg})
-        store = mock.Mock()
+        store = mock.MagicMock()
         self._do_revoke_test_helper(response, content, error_msg,
                                     store=store)
 
@@ -1364,61 +1380,70 @@
         credentials = client.OAuth2Credentials(
             None, None, None, None, None, None, None,
             token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI)
-        http = http_mock.HttpMock(headers=response, data=content)
+        http_request = mock.Mock()
+        http_request.return_value = response, content
         token = u's3kr3tz'
 
         if response.status == http_client.OK:
             self.assertEqual(credentials.scopes, set())
             self.assertIsNone(
-                credentials._do_retrieve_scopes(http, token))
+                credentials._do_retrieve_scopes(http_request, token))
             self.assertEqual(credentials.scopes, scopes)
         else:
             self.assertEqual(credentials.scopes, set())
             with self.assertRaises(client.Error) as exc_manager:
-                credentials._do_retrieve_scopes(http, token)
+                credentials._do_retrieve_scopes(http_request, token)
             # Make sure scopes were not changed.
             self.assertEqual(credentials.scopes, set())
             self.assertEqual(exc_manager.exception.args, (error_msg,))
 
-        token_uri = _helpers.update_query_params(
+        token_uri = client._update_query_params(
             oauth2client.GOOGLE_TOKEN_INFO_URI,
             {'fields': 'scope', 'access_token': token})
-
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        assertUrisEqual(self, token_uri, http.uri)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertIsNone(http.headers)
+        self.assertEqual(len(http_request.mock_calls), 1)
+        scopes_call = http_request.mock_calls[0]
+        call_args = scopes_call[1]
+        self.assertEqual(len(call_args), 1)
+        called_uri = call_args[0]
+        assertUrisEqual(self, token_uri, called_uri)
         logger.info.assert_called_once_with('Refreshing scopes')
 
     def test__do_retrieve_scopes_success_bad_json(self):
-        response = http_mock.ResponseMock()
+        response = httplib2.Response({
+            'status': http_client.OK,
+        })
         invalid_json = b'{'
         with self.assertRaises(ValueError):
             self._do_retrieve_scopes_test_helper(response, invalid_json, None)
 
     def test__do_retrieve_scopes_success(self):
-        response = http_mock.ResponseMock()
+        response = httplib2.Response({
+            'status': http_client.OK,
+        })
         content = b'{"scope": "foo bar"}'
         self._do_retrieve_scopes_test_helper(response, content, None,
                                              scopes=set(['foo', 'bar']))
 
     def test__do_retrieve_scopes_non_json_failure(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_REQUEST})
+        response = httplib2.Response({
+            'status': http_client.BAD_REQUEST,
+        })
         content = u'Bad request'
         error_msg = 'Invalid response {0}.'.format(response.status)
         self._do_retrieve_scopes_test_helper(response, content, error_msg)
 
     def test__do_retrieve_scopes_basic_failure(self):
-        response = http_mock.ResponseMock(
-            {'status': http_client.INTERNAL_SERVER_ERROR})
+        response = httplib2.Response({
+            'status': http_client.INTERNAL_SERVER_ERROR,
+        })
         content = u'{}'
         error_msg = 'Invalid response {0}.'.format(response.status)
         self._do_retrieve_scopes_test_helper(response, content, error_msg)
 
     def test__do_retrieve_scopes_failure_w_json_error(self):
-        response = http_mock.ResponseMock({'status': http_client.BAD_GATEWAY})
+        response = httplib2.Response({
+            'status': http_client.BAD_GATEWAY,
+        })
         error_msg = 'Error desc I sit at a desk'
         content = json.dumps({'error_description': error_msg})
         self._do_retrieve_scopes_test_helper(response, content, error_msg)
@@ -1442,7 +1467,7 @@
     def test_retrieve_scopes(self):
         info_response_first = {'scope': 'foo bar'}
         info_response_second = {'error_description': 'abcdef'}
-        http = http_mock.HttpMockSequence([
+        http = HttpMockSequence([
             ({'status': '200'}, json.dumps(info_response_first).encode(
                 'utf-8')),
             ({'status': '400'}, json.dumps(info_response_second).encode(
@@ -1471,18 +1496,17 @@
                               b'  "expires_in":3600,'
                               b'  "id_token": "' + jwt + b'"'
                               b'}')
-            http = http_mock.HttpMockSequence([
+            http = HttpMockSequence([
                 ({'status': status_code}, b''),
                 ({'status': '200'}, token_response),
                 ({'status': '200'}, 'echo_request_headers'),
             ])
             http = self.credentials.authorize(http)
-            resp, content = transport.request(http, 'http://example.com')
+            resp, content = http.request('http://example.com')
             self.assertEqual(self.credentials.id_token, body)
-            self.assertEqual(self.credentials.id_token_jwt, jwt.decode())
 
 
-class AccessTokenCredentialsTests(unittest.TestCase):
+class AccessTokenCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
         access_token = 'foo'
@@ -1493,40 +1517,41 @@
 
     def test_token_refresh_success(self):
         for status_code in client.REFRESH_STATUS_CODES:
-            http = http_mock.HttpMock(
-                headers={'status': status_code}, data=b'')
+            http = HttpMockSequence([
+                ({'status': status_code}, b''),
+            ])
             http = self.credentials.authorize(http)
             with self.assertRaises(client.AccessTokenCredentialsError):
-                resp, content = transport.request(http, 'http://example.com')
+                resp, content = http.request('http://example.com')
 
     def test_token_revoke_success(self):
-        http = http_mock.HttpMock(headers={'status': http_client.OK})
         _token_revoke_test_helper(
-            self, revoke_raise=False, valid_bool_value=True,
-            token_attr='access_token', http_mock=http)
+            self, '200', revoke_raise=False,
+            valid_bool_value=True, token_attr='access_token')
 
     def test_token_revoke_failure(self):
-        http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST})
         _token_revoke_test_helper(
-            self, revoke_raise=True, valid_bool_value=False,
-            token_attr='access_token', http_mock=http)
+            self, '400', revoke_raise=True,
+            valid_bool_value=False, token_attr='access_token')
 
     def test_non_401_error_response(self):
-        http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST})
+        http = HttpMockSequence([
+            ({'status': '400'}, b''),
+        ])
         http = self.credentials.authorize(http)
-        resp, content = transport.request(http, 'http://example.com')
+        resp, content = http.request('http://example.com')
         self.assertEqual(http_client.BAD_REQUEST, resp.status)
 
     def test_auth_header_sent(self):
-        http = http_mock.HttpMockSequence([
+        http = HttpMockSequence([
             ({'status': '200'}, 'echo_request_headers'),
         ])
         http = self.credentials.authorize(http)
-        resp, content = transport.request(http, 'http://example.com')
+        resp, content = http.request('http://example.com')
         self.assertEqual(b'Bearer foo', content[b'Authorization'])
 
 
-class TestAssertionCredentials(unittest.TestCase):
+class TestAssertionCredentials(unittest2.TestCase):
     assertion_text = 'This is the assertion'
     assertion_type = 'http://www.google.com/assertionType'
 
@@ -1553,25 +1578,23 @@
                          body['grant_type'][0])
 
     def test_assertion_refresh(self):
-        http = http_mock.HttpMockSequence([
+        http = HttpMockSequence([
             ({'status': '200'}, b'{"access_token":"1/3w"}'),
             ({'status': '200'}, 'echo_request_headers'),
         ])
         http = self.credentials.authorize(http)
-        resp, content = transport.request(http, 'http://example.com')
+        resp, content = http.request('http://example.com')
         self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
 
     def test_token_revoke_success(self):
-        http = http_mock.HttpMock(headers={'status': http_client.OK})
         _token_revoke_test_helper(
-            self, revoke_raise=False, valid_bool_value=True,
-            token_attr='access_token', http_mock=http)
+            self, '200', revoke_raise=False,
+            valid_bool_value=True, token_attr='access_token')
 
     def test_token_revoke_failure(self):
-        http = http_mock.HttpMock(headers={'status': http_client.BAD_REQUEST})
         _token_revoke_test_helper(
-            self, revoke_raise=True, valid_bool_value=False,
-            token_attr='access_token', http_mock=http)
+            self, '400', revoke_raise=True,
+            valid_bool_value=False, token_attr='access_token')
 
     def test_sign_blob_abstract(self):
         credentials = client.AssertionCredentials(None)
@@ -1579,7 +1602,20 @@
             credentials.sign_blob(b'blob')
 
 
-class ExtractIdTokenTest(unittest.TestCase):
+class UpdateQueryParamsTest(unittest2.TestCase):
+    def test_update_query_params_no_params(self):
+        uri = 'http://www.google.com'
+        updated = client._update_query_params(uri, {'a': 'b'})
+        self.assertEqual(updated, uri + '?a=b')
+
+    def test_update_query_params_existing_params(self):
+        uri = 'http://www.google.com?x=y'
+        updated = client._update_query_params(uri, {'a': 'b', 'c': 'd&'})
+        hardcoded_update = uri + '&a=b&c=d%26'
+        assertUrisEqual(self, updated, hardcoded_update)
+
+
+class ExtractIdTokenTest(unittest2.TestCase):
     """Tests client._extract_id_token()."""
 
     def test_extract_success(self):
@@ -1600,7 +1636,7 @@
             client._extract_id_token(jwt)
 
 
-class OAuth2WebServerFlowTest(unittest.TestCase):
+class OAuth2WebServerFlowTest(unittest2.TestCase):
 
     def setUp(self):
         self.flow = client.OAuth2WebServerFlow(
@@ -1611,9 +1647,6 @@
             user_agent='unittest-sample/1.0',
             revoke_uri='dummy_revoke_uri',
         )
-        self.bad_verifier = b'__NOT_THE_VERIFIER_YOURE_LOOKING_FOR__'
-        self.good_verifier = b'__TEST_VERIFIER__'
-        self.good_challenger = b'__TEST_CHALLENGE__'
 
     def test_construct_authorize_url(self):
         authorize_url = self.flow.step1_get_authorize_url(state='state+1')
@@ -1678,51 +1711,11 @@
             'access_type': 'offline',
             'response_type': 'code',
         }
-        expected = _helpers.update_query_params(flow.auth_uri, query_params)
+        expected = client._update_query_params(flow.auth_uri, query_params)
         assertUrisEqual(self, expected, result)
         # Check stubs.
         self.assertEqual(logger.warning.call_count, 1)
 
-    @mock.patch('oauth2client.client._pkce.code_challenge')
-    @mock.patch('oauth2client.client._pkce.code_verifier')
-    def test_step1_get_authorize_url_pkce(self, fake_verifier, fake_challenge):
-        fake_verifier.return_value = self.good_verifier
-        fake_challenge.return_value = self.good_challenger
-        flow = client.OAuth2WebServerFlow(
-            'client_id+1',
-            scope='foo',
-            redirect_uri='http://example.com',
-            pkce=True)
-        auth_url = urllib.parse.urlparse(flow.step1_get_authorize_url())
-        self.assertEqual(flow.code_verifier, self.good_verifier)
-        results = dict(urllib.parse.parse_qsl(auth_url.query))
-        self.assertEqual(
-            results['code_challenge'], self.good_challenger.decode())
-        self.assertEqual(results['code_challenge_method'], 'S256')
-        fake_verifier.assert_called()
-        fake_challenge.assert_called_with(self.good_verifier)
-
-    @mock.patch('oauth2client.client._pkce.code_challenge')
-    @mock.patch('oauth2client.client._pkce.code_verifier')
-    def test_step1_get_authorize_url_pkce_invalid_verifier(
-            self, fake_verifier, fake_challenge):
-        fake_verifier.return_value = self.good_verifier
-        fake_challenge.return_value = self.good_challenger
-        flow = client.OAuth2WebServerFlow(
-            'client_id+1',
-            scope='foo',
-            redirect_uri='http://example.com',
-            pkce=True,
-            code_verifier=self.bad_verifier)
-        auth_url = urllib.parse.urlparse(flow.step1_get_authorize_url())
-        self.assertEqual(flow.code_verifier, self.bad_verifier)
-        results = dict(urllib.parse.parse_qsl(auth_url.query))
-        self.assertEqual(
-            results['code_challenge'], self.good_challenger.decode())
-        self.assertEqual(results['code_challenge_method'], 'S256')
-        fake_verifier.assert_not_called()
-        fake_challenge.assert_called_with(self.bad_verifier)
-
     def test_step1_get_authorize_url_without_redirect(self):
         flow = client.OAuth2WebServerFlow('client_id+1', scope='foo',
                                           redirect_uri=None)
@@ -1743,7 +1736,7 @@
             'access_type': 'offline',
             'response_type': 'code',
         }
-        expected = _helpers.update_query_params(flow.auth_uri, query_params)
+        expected = client._update_query_params(flow.auth_uri, query_params)
         assertUrisEqual(self, expected, result)
 
     def test_step1_get_device_and_user_codes_wo_device_uri(self):
@@ -1765,15 +1758,12 @@
                 'user_code': user_code,
                 'verification_url': ver_url,
             })
-        http = http_mock.HttpMockSequence([
+        http = HttpMockSequence([
             ({'status': http_client.OK}, content),
         ])
         if default_http:
-            with mock.patch('oauth2client.transport.get_http_object',
-                            return_value=http) as new_http:
+            with mock.patch('httplib2.Http', return_value=http):
                 result = flow.step1_get_device_and_user_codes()
-                # Check the mock was called.
-                new_http.assert_called_once_with()
         else:
             result = flow.step1_get_device_and_user_codes(http=http)
 
@@ -1781,17 +1771,16 @@
             device_code, user_code, None, ver_url, None)
         self.assertEqual(result, expected)
         self.assertEqual(len(http.requests), 1)
-        info = http.requests[0]
-        self.assertEqual(info['uri'], oauth2client.GOOGLE_DEVICE_URI)
-        expected_body = {
-            'client_id': [flow.client_id],
-            'scope': [flow.scope],
-        }
-        self.assertEqual(urllib.parse.parse_qs(info['body']), expected_body)
+        self.assertEqual(
+            http.requests[0]['uri'], oauth2client.GOOGLE_DEVICE_URI)
+        body = http.requests[0]['body']
+        self.assertEqual(urllib.parse.parse_qs(body),
+                         {'client_id': [flow.client_id],
+                          'scope': [flow.scope]})
         headers = {'content-type': 'application/x-www-form-urlencoded'}
         if extra_headers is not None:
             headers.update(extra_headers)
-        self.assertEqual(info['headers'], headers)
+        self.assertEqual(http.requests[0]['headers'], headers)
 
     def test_step1_get_device_and_user_codes(self):
         self._step1_get_device_and_user_codes_helper()
@@ -1814,7 +1803,9 @@
     def _step1_get_device_and_user_codes_fail_helper(self, status,
                                                      content, error_msg):
         flow = client.OAuth2WebServerFlow('CID', scope='foo')
-        http = http_mock.HttpMock(headers={'status': status}, data=content)
+        http = HttpMockSequence([
+            ({'status': status}, content),
+        ])
         with self.assertRaises(client.OAuth2DeviceCodeError) as exc_manager:
             flow.step1_get_device_and_user_codes(http=http)
 
@@ -1858,19 +1849,17 @@
             client.OAuth2WebServerFlow('client_id+1')
 
     def test_exchange_failure(self):
-        http = http_mock.HttpMock(
-            headers={'status': http_client.BAD_REQUEST},
-            data=b'{"error":"invalid_request"}',
-        )
+        http = HttpMockSequence([
+            ({'status': '400'}, b'{"error":"invalid_request"}'),
+        ])
 
         with self.assertRaises(client.FlowExchangeError):
             self.flow.step2_exchange(code='some random code', http=http)
 
     def test_urlencoded_exchange_failure(self):
-        http = http_mock.HttpMock(
-            headers={'status': http_client.BAD_REQUEST},
-            data=b'error=invalid_request',
-        )
+        http = HttpMockSequence([
+            ({'status': '400'}, b'error=invalid_request'),
+        ])
 
         with self.assertRaisesRegexp(client.FlowExchangeError,
                                      'invalid_request'):
@@ -1887,7 +1876,7 @@
                    b'    "type": "OAuthException"'
                    b'  }'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '400'}, payload)])
 
         with self.assertRaises(client.FlowExchangeError):
             self.flow.step2_exchange(code='some random code', http=http)
@@ -1898,7 +1887,7 @@
                    b'  "expires_in":3600,'
                    b'  "refresh_token":"8xLOxBtZp8"'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
         credentials = self.flow.step2_exchange(
             code=code, device_flow_info=device_flow_info, http=http)
         self.assertEqual('SlAV32hkKG', credentials.access_token)
@@ -1927,7 +1916,8 @@
                    '  "expires_in":' + expires_in + ','
                    '  "refresh_token":"' + refresh_token + '"'
                    '}')
-        http = http_mock.HttpMock(data=_helpers._to_bytes(payload))
+        http = HttpMockSequence(
+            [({'status': '200'}, _helpers._to_bytes(payload))])
         credentials = self.flow.step2_exchange(code=binary_code, http=http)
         self.assertEqual(access_token, credentials.access_token)
         self.assertIsNotNone(credentials.token_expiry)
@@ -1953,9 +1943,7 @@
                    b'  "expires_in":3600,'
                    b'  "refresh_token":"8xLOxBtZp8"'
                    b'}')
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, payload),
-        ])
+        http = HttpMockSequence([({'status': '200'}, payload)])
 
         credentials = self.flow.step2_exchange(code=not_a_dict, http=http)
         self.assertEqual('SlAV32hkKG', credentials.access_token)
@@ -1963,29 +1951,10 @@
         self.assertEqual('8xLOxBtZp8', credentials.refresh_token)
         self.assertEqual('dummy_revoke_uri', credentials.revoke_uri)
         self.assertEqual(set(['foo']), credentials.scopes)
-        self.assertEqual(len(http.requests), 1)
         request_code = urllib.parse.parse_qs(
             http.requests[0]['body'])['code'][0]
         self.assertEqual(code, request_code)
 
-    def test_exchange_with_pkce(self):
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b'access_token=SlAV32hkKG'),
-        ])
-        flow = client.OAuth2WebServerFlow(
-            'client_id+1',
-            scope='foo',
-            redirect_uri='http://example.com',
-            pkce=True,
-            code_verifier=self.good_verifier)
-        flow.step2_exchange(code='some random code', http=http)
-
-        self.assertEqual(len(http.requests), 1)
-        test_request = http.requests[0]
-        self.assertIn(
-            'code_verifier={0}'.format(self.good_verifier.decode()),
-            test_request['body'])
-
     def test_exchange_using_authorization_header(self):
         auth_header = 'Basic Y2xpZW50X2lkKzE6c2Vjexc_managerV0KzE=',
         flow = client.OAuth2WebServerFlow(
@@ -1996,14 +1965,13 @@
             user_agent='unittest-sample/1.0',
             revoke_uri='dummy_revoke_uri',
         )
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b'access_token=SlAV32hkKG'),
+        http = HttpMockSequence([
+            ({'status': '200'}, b'access_token=SlAV32hkKG'),
         ])
 
         credentials = flow.step2_exchange(code='some random code', http=http)
         self.assertEqual('SlAV32hkKG', credentials.access_token)
 
-        self.assertEqual(len(http.requests), 1)
         test_request = http.requests[0]
         # Did we pass the Authorization header?
         self.assertEqual(test_request['headers']['Authorization'], auth_header)
@@ -2011,8 +1979,9 @@
         self.assertTrue('client_secret' not in test_request['body'])
 
     def test_urlencoded_exchange_success(self):
-        http = http_mock.HttpMock(
-            data=b'access_token=SlAV32hkKG&expires_in=3600')
+        http = HttpMockSequence([
+            ({'status': '200'}, b'access_token=SlAV32hkKG&expires_in=3600'),
+        ])
 
         credentials = self.flow.step2_exchange(code='some random code',
                                                http=http)
@@ -2020,9 +1989,12 @@
         self.assertNotEqual(None, credentials.token_expiry)
 
     def test_urlencoded_expires_param(self):
-        # Note the 'expires=3600' where you'd normally
-        # have if named 'expires_in'
-        http = http_mock.HttpMock(data=b'access_token=SlAV32hkKG&expires=3600')
+        http = HttpMockSequence([
+            # Note the 'expires=3600' where you'd normally
+            # have if named 'expires_in'
+            ({'status': '200'}, b'access_token=SlAV32hkKG&expires=3600'),
+        ])
+
         credentials = self.flow.step2_exchange(code='some random code',
                                                http=http)
         self.assertNotEqual(None, credentials.token_expiry)
@@ -2032,16 +2004,18 @@
                    b'  "access_token":"SlAV32hkKG",'
                    b'  "refresh_token":"8xLOxBtZp8"'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
 
         credentials = self.flow.step2_exchange(code='some random code',
                                                http=http)
         self.assertEqual(None, credentials.token_expiry)
 
     def test_urlencoded_exchange_no_expires_in(self):
-        # This might be redundant but just to make sure
-        # urlencoded access_token gets parsed correctly
-        http = http_mock.HttpMock(data=b'access_token=SlAV32hkKG')
+        http = HttpMockSequence([
+            # This might be redundant but just to make sure
+            # urlencoded access_token gets parsed correctly
+            ({'status': '200'}, b'access_token=SlAV32hkKG'),
+        ])
 
         credentials = self.flow.step2_exchange(code='some random code',
                                                http=http)
@@ -2052,7 +2026,7 @@
                    b'  "access_token":"SlAV32hkKG",'
                    b'  "refresh_token":"8xLOxBtZp8"'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
 
         code = {'error': 'thou shall not pass'}
         with self.assertRaisesRegexp(
@@ -2065,7 +2039,7 @@
                    b'  "refresh_token":"8xLOxBtZp8",'
                    b'  "id_token": "stuff.payload"'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
 
         with self.assertRaises(client.VerifyJwtTokenError):
             self.flow.step2_exchange(code='some random code', http=http)
@@ -2082,17 +2056,16 @@
                    b'  "refresh_token":"8xLOxBtZp8",'
                    b'  "id_token": "' + jwt + b'"'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
         credentials = self.flow.step2_exchange(code='some random code',
                                                http=http)
         self.assertEqual(credentials.id_token, body)
-        self.assertEqual(credentials.id_token_jwt, jwt.decode())
 
 
-class FlowFromCachedClientsecrets(unittest.TestCase):
+class FlowFromCachedClientsecrets(unittest2.TestCase):
 
     def test_flow_from_clientsecrets_cached(self):
-        cache_mock = http_mock.CacheMock()
+        cache_mock = CacheMock()
         load_and_cache('client_secrets.json', 'some_secrets', cache_mock)
 
         flow = client.flow_from_clientsecrets(
@@ -2195,7 +2168,7 @@
         loadfile_mock.assert_called_once_with(filename, cache=cache)
 
 
-class CredentialsFromCodeTests(unittest.TestCase):
+class CredentialsFromCodeTests(unittest2.TestCase):
 
     def setUp(self):
         self.client_id = 'client_id_abc'
@@ -2207,7 +2180,9 @@
     def test_exchange_code_for_token(self):
         token = 'asdfghjkl'
         payload = json.dumps({'access_token': token, 'expires_in': 3600})
-        http = http_mock.HttpMock(data=payload.encode('utf-8'))
+        http = HttpMockSequence([
+            ({'status': '200'}, payload.encode('utf-8')),
+        ])
         credentials = client.credentials_from_code(
             self.client_id, self.client_secret, self.scope,
             self.code, http=http, redirect_uri=self.redirect_uri)
@@ -2216,10 +2191,9 @@
         self.assertEqual(set(['foo']), credentials.scopes)
 
     def test_exchange_code_for_token_fail(self):
-        http = http_mock.HttpMock(
-            headers={'status': http_client.BAD_REQUEST},
-            data=b'{"error":"invalid_request"}',
-        )
+        http = HttpMockSequence([
+            ({'status': '400'}, b'{"error":"invalid_request"}'),
+        ])
 
         with self.assertRaises(client.FlowExchangeError):
             client.credentials_from_code(
@@ -2231,7 +2205,7 @@
                    b'  "access_token":"asdfghjkl",'
                    b'  "expires_in":3600'
                    b'}')
-        http = http_mock.HttpMock(data=payload)
+        http = HttpMockSequence([({'status': '200'}, payload)])
         credentials = client.credentials_from_clientsecrets_and_code(
             datafile('client_secrets.json'), self.scope,
             self.code, http=http)
@@ -2240,8 +2214,10 @@
         self.assertEqual(set(['foo']), credentials.scopes)
 
     def test_exchange_code_and_cached_file_for_token(self):
-        http = http_mock.HttpMock(data=b'{ "access_token":"asdfghjkl"}')
-        cache_mock = http_mock.CacheMock()
+        http = HttpMockSequence([
+            ({'status': '200'}, b'{ "access_token":"asdfghjkl"}'),
+        ])
+        cache_mock = CacheMock()
         load_and_cache('client_secrets.json', 'some_secrets', cache_mock)
 
         credentials = client.credentials_from_clientsecrets_and_code(
@@ -2251,10 +2227,9 @@
         self.assertEqual(set(['foo']), credentials.scopes)
 
     def test_exchange_code_and_file_for_token_fail(self):
-        http = http_mock.HttpMock(
-            headers={'status': http_client.BAD_REQUEST},
-            data=b'{"error":"invalid_request"}',
-        )
+        http = HttpMockSequence([
+            ({'status': '400'}, b'{"error":"invalid_request"}'),
+        ])
 
         with self.assertRaises(client.FlowExchangeError):
             client.credentials_from_clientsecrets_and_code(
@@ -2262,7 +2237,7 @@
                 self.code, http=http)
 
 
-class Test__save_private_file(unittest.TestCase):
+class Test__save_private_file(unittest2.TestCase):
 
     def _save_helper(self, filename):
         contents = []
@@ -2290,7 +2265,7 @@
         self._save_helper(filename)
 
 
-class Test__get_application_default_credential_GAE(unittest.TestCase):
+class Test__get_application_default_credential_GAE(unittest2.TestCase):
 
     @mock.patch.dict('sys.modules', {
         'oauth2client.contrib.appengine': mock.Mock()})
@@ -2303,7 +2278,7 @@
         creds_kls.assert_called_once_with([])
 
 
-class Test__get_application_default_credential_GCE(unittest.TestCase):
+class Test__get_application_default_credential_GCE(unittest2.TestCase):
 
     @mock.patch.dict('sys.modules', {
         'oauth2client.contrib.gce': mock.Mock()})
@@ -2316,7 +2291,7 @@
         creds_kls.assert_called_once_with()
 
 
-class Test__require_crypto_or_die(unittest.TestCase):
+class Test__require_crypto_or_die(unittest2.TestCase):
 
     @mock.patch.object(client, 'HAS_CRYPTO', new=True)
     def test_with_crypto(self):
@@ -2328,7 +2303,7 @@
             client._require_crypto_or_die()
 
 
-class TestDeviceFlowInfo(unittest.TestCase):
+class TestDeviceFlowInfo(unittest2.TestCase):
 
     DEVICE_CODE = 'e80ff179-fd65-416c-9dbf-56a23e5d23e4'
     USER_CODE = '4bbd8b82-fc73-11e5-adf3-00c2c63e5792'
diff --git a/tests/test_clientsecrets.py b/tests/test_clientsecrets.py
index 3fa9c30..42eb8c7 100644
--- a/tests/test_clientsecrets.py
+++ b/tests/test_clientsecrets.py
@@ -18,13 +18,17 @@
 from io import StringIO
 import os
 import tempfile
-import unittest
+
+import unittest2
 
 import oauth2client
 from oauth2client import _helpers
 from oauth2client import clientsecrets
 
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
 DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
 VALID_FILE = os.path.join(DATA_DIR, 'client_secrets.json')
 INVALID_FILE = os.path.join(DATA_DIR, 'unfilled_client_secrets.json')
@@ -32,7 +36,7 @@
     os.path.dirname(__file__), 'afilethatisntthere.json')
 
 
-class Test__validate_clientsecrets(unittest.TestCase):
+class Test__validate_clientsecrets(unittest2.TestCase):
 
     def test_with_none(self):
         with self.assertRaises(clientsecrets.InvalidClientSecretsError):
@@ -143,7 +147,7 @@
         self.assertEqual(result, (clientsecrets.TYPE_INSTALLED, client_info))
 
 
-class Test__loadfile(unittest.TestCase):
+class Test__loadfile(unittest2.TestCase):
 
     def test_success(self):
         client_type, client_info = clientsecrets._loadfile(VALID_FILE)
@@ -172,7 +176,7 @@
             clientsecrets._loadfile(filename)
 
 
-class OAuth2CredentialsTests(unittest.TestCase):
+class OAuth2CredentialsTests(unittest2.TestCase):
 
     def test_validate_error(self):
         payload = (
@@ -219,7 +223,7 @@
         self.assertEquals(exc_manager.exception.args[3], errno.ENOENT)
 
 
-class CachedClientsecretsTests(unittest.TestCase):
+class CachedClientsecretsTests(unittest2.TestCase):
 
     class CacheMock(object):
         def __init__(self):
diff --git a/tests/test_crypt.py b/tests/test_crypt.py
index bc56697..b7534bd 100644
--- a/tests/test_crypt.py
+++ b/tests/test_crypt.py
@@ -14,9 +14,9 @@
 
 import base64
 import os
-import unittest
 
 import mock
+import unittest2
 
 from oauth2client import _helpers
 from oauth2client import client
@@ -33,14 +33,14 @@
         return file_obj.read()
 
 
-class Test__bad_pkcs12_key_as_pem(unittest.TestCase):
+class Test__bad_pkcs12_key_as_pem(unittest2.TestCase):
 
     def test_fails(self):
         with self.assertRaises(NotImplementedError):
             crypt._bad_pkcs12_key_as_pem()
 
 
-class Test_pkcs12_key_as_pem(unittest.TestCase):
+class Test_pkcs12_key_as_pem(unittest2.TestCase):
 
     def _make_svc_account_creds(self, private_key_file='privatekey.p12'):
         filename = data_filename(private_key_file)
@@ -72,7 +72,7 @@
         self._succeeds_helper(password)
 
 
-class Test__verify_signature(unittest.TestCase):
+class Test__verify_signature(unittest2.TestCase):
 
     def test_success_single_cert(self):
         cert_value = 'cert-value'
@@ -80,11 +80,11 @@
         message = object()
         signature = object()
 
-        verifier = mock.Mock()
-        verifier.verify = mock.Mock(name='verify', return_value=True)
+        verifier = mock.MagicMock()
+        verifier.verify = mock.MagicMock(name='verify', return_value=True)
         with mock.patch('oauth2client.crypt.Verifier') as Verifier:
-            Verifier.from_string = mock.Mock(name='from_string',
-                                             return_value=verifier)
+            Verifier.from_string = mock.MagicMock(name='from_string',
+                                                  return_value=verifier)
             result = crypt._verify_signature(message, signature, certs)
             self.assertEqual(result, None)
 
@@ -101,14 +101,14 @@
         message = object()
         signature = object()
 
-        verifier = mock.Mock()
+        verifier = mock.MagicMock()
         # Use side_effect to force all 3 cert values to be used by failing
         # to verify on the first two.
-        verifier.verify = mock.Mock(name='verify',
-                                    side_effect=[False, False, True])
+        verifier.verify = mock.MagicMock(name='verify',
+                                         side_effect=[False, False, True])
         with mock.patch('oauth2client.crypt.Verifier') as Verifier:
-            Verifier.from_string = mock.Mock(name='from_string',
-                                             return_value=verifier)
+            Verifier.from_string = mock.MagicMock(name='from_string',
+                                                  return_value=verifier)
             result = crypt._verify_signature(message, signature, certs)
             self.assertEqual(result, None)
 
@@ -130,11 +130,11 @@
         message = object()
         signature = object()
 
-        verifier = mock.Mock()
-        verifier.verify = mock.Mock(name='verify', return_value=False)
+        verifier = mock.MagicMock()
+        verifier.verify = mock.MagicMock(name='verify', return_value=False)
         with mock.patch('oauth2client.crypt.Verifier') as Verifier:
-            Verifier.from_string = mock.Mock(name='from_string',
-                                             return_value=verifier)
+            Verifier.from_string = mock.MagicMock(name='from_string',
+                                                  return_value=verifier)
             with self.assertRaises(crypt.AppIdentityError):
                 crypt._verify_signature(message, signature, certs)
 
@@ -144,7 +144,7 @@
             verifier.verify.assert_called_once_with(message, signature)
 
 
-class Test__check_audience(unittest.TestCase):
+class Test__check_audience(unittest2.TestCase):
 
     def test_null_audience(self):
         result = crypt._check_audience(None, None)
@@ -172,7 +172,7 @@
             crypt._check_audience(payload_dict, audience2)
 
 
-class Test__verify_time_range(unittest.TestCase):
+class Test__verify_time_range(unittest2.TestCase):
 
     def _exception_helper(self, payload_dict):
         exception_caught = None
@@ -204,8 +204,8 @@
             'exp': current_time + crypt.MAX_TOKEN_LIFETIME_SECS + 1,
         }
         with mock.patch('oauth2client.crypt.time') as time:
-            time.time = mock.Mock(name='time',
-                                  return_value=current_time)
+            time.time = mock.MagicMock(name='time',
+                                       return_value=current_time)
 
             exception_caught = self._exception_helper(payload_dict)
             self.assertNotEqual(exception_caught, None)
@@ -219,8 +219,8 @@
             'exp': current_time + crypt.MAX_TOKEN_LIFETIME_SECS - 1,
         }
         with mock.patch('oauth2client.crypt.time') as time:
-            time.time = mock.Mock(name='time',
-                                  return_value=current_time)
+            time.time = mock.MagicMock(name='time',
+                                       return_value=current_time)
 
             exception_caught = self._exception_helper(payload_dict)
             self.assertNotEqual(exception_caught, None)
@@ -234,8 +234,8 @@
             'exp': current_time - crypt.CLOCK_SKEW_SECS - 1,
         }
         with mock.patch('oauth2client.crypt.time') as time:
-            time.time = mock.Mock(name='time',
-                                  return_value=current_time)
+            time.time = mock.MagicMock(name='time',
+                                       return_value=current_time)
 
             exception_caught = self._exception_helper(payload_dict)
             self.assertNotEqual(exception_caught, None)
@@ -249,14 +249,14 @@
             'exp': current_time + crypt.MAX_TOKEN_LIFETIME_SECS - 1,
         }
         with mock.patch('oauth2client.crypt.time') as time:
-            time.time = mock.Mock(name='time',
-                                  return_value=current_time)
+            time.time = mock.MagicMock(name='time',
+                                       return_value=current_time)
 
             exception_caught = self._exception_helper(payload_dict)
             self.assertEqual(exception_caught, None)
 
 
-class Test_verify_signed_jwt_with_certs(unittest.TestCase):
+class Test_verify_signed_jwt_with_certs(unittest2.TestCase):
 
     def test_jwt_no_segments(self):
         exception_caught = None
@@ -288,10 +288,10 @@
     @mock.patch('oauth2client.crypt._verify_time_range')
     @mock.patch('oauth2client.crypt._verify_signature')
     def test_success(self, verify_sig, verify_time, check_aud):
-        certs = mock.Mock()
+        certs = mock.MagicMock()
         cert_values = object()
-        certs.values = mock.Mock(name='values',
-                                 return_value=cert_values)
+        certs.values = mock.MagicMock(name='values',
+                                      return_value=cert_values)
         audience = object()
 
         header = b'header'
diff --git a/tests/test_file.py b/tests/test_file.py
index 80324d6..924acb4 100644
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -12,7 +12,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Unit tests for oauth2client.file."""
+"""Oauth2client.file tests
+
+Unit tests for oauth2client.file
+"""
 
 import copy
 import datetime
@@ -21,31 +24,28 @@
 import pickle
 import stat
 import tempfile
-import unittest
-import warnings
 
-import mock
 import six
 from six.moves import http_client
-from six.moves import urllib_parse
+import unittest2
 
-from oauth2client import _helpers
 from oauth2client import client
-from oauth2client import file as file_module
-from oauth2client import transport
-from tests import http_mock
+from oauth2client import file
+from .http_mock import HttpMockSequence
 
 try:
     # Python2
     from future_builtins import oct
-except ImportError:  # pragma: NO COVER
+except:  # pragma: NO COVER
     pass
 
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
 _filehandle, FILENAME = tempfile.mkstemp('oauth2client_test.data')
 os.close(_filehandle)
 
 
-class OAuth2ClientFileTests(unittest.TestCase):
+class OAuth2ClientFileTests(unittest2.TestCase):
 
     def tearDown(self):
         try:
@@ -54,7 +54,6 @@
             pass
 
     def setUp(self):
-        warnings.simplefilter("ignore")
         try:
             os.unlink(FILENAME)
         except OSError:
@@ -75,31 +74,19 @@
             user_agent)
         return credentials
 
-    @mock.patch('warnings.warn')
-    def test_non_existent_file_storage(self, warn_mock):
-        storage = file_module.Storage(FILENAME)
-        credentials = storage.get()
-        warn_mock.assert_called_with(
-            _helpers._MISSING_FILE_MESSAGE.format(FILENAME))
-        self.assertIsNone(credentials)
+    def test_non_existent_file_storage(self):
+        s = file.Storage(FILENAME)
+        credentials = s.get()
+        self.assertEquals(None, credentials)
 
-    def test_directory_file_storage(self):
-        storage = file_module.Storage(FILENAME)
-        os.mkdir(FILENAME)
-        try:
-            with self.assertRaises(IOError):
-                storage.get()
-        finally:
-            os.rmdir(FILENAME)
-
-    @unittest.skipIf(not hasattr(os, 'symlink'), 'No symlink available')
+    @unittest2.skipIf(not hasattr(os, 'symlink'), 'No symlink available')
     def test_no_sym_link_credentials(self):
         SYMFILENAME = FILENAME + '.sym'
         os.symlink(FILENAME, SYMFILENAME)
-        storage = file_module.Storage(SYMFILENAME)
+        s = file.Storage(SYMFILENAME)
         try:
-            with self.assertRaises(IOError):
-                storage.get()
+            with self.assertRaises(file.CredentialsFileSymbolicLinkError):
+                s.get()
         finally:
             os.unlink(SYMFILENAME)
 
@@ -107,20 +94,20 @@
         # Write a file with a pickled OAuth2Credentials.
         credentials = self._create_test_credentials()
 
-        credentials_file = open(FILENAME, 'wb')
-        pickle.dump(credentials, credentials_file)
-        credentials_file.close()
+        f = open(FILENAME, 'wb')
+        pickle.dump(credentials, f)
+        f.close()
 
         # Storage should be not be able to read that object, as the capability
         # to read and write credentials as pickled objects has been removed.
-        storage = file_module.Storage(FILENAME)
-        read_credentials = storage.get()
-        self.assertIsNone(read_credentials)
+        s = file.Storage(FILENAME)
+        read_credentials = s.get()
+        self.assertEquals(None, read_credentials)
 
         # Now write it back out and confirm it has been rewritten as JSON
-        storage.put(credentials)
-        with open(FILENAME) as credentials_file:
-            data = json.load(credentials_file)
+        s.put(credentials)
+        with open(FILENAME) as f:
+            data = json.load(f)
 
         self.assertEquals(data['access_token'], 'foo')
         self.assertEquals(data['_class'], 'OAuth2Credentials')
@@ -131,38 +118,22 @@
                       datetime.timedelta(minutes=15))
         credentials = self._create_test_credentials(expiration=expiration)
 
-        storage = file_module.Storage(FILENAME)
-        storage.put(credentials)
-        credentials = storage.get()
+        s = file.Storage(FILENAME)
+        s.put(credentials)
+        credentials = s.get()
         new_cred = copy.copy(credentials)
         new_cred.access_token = 'bar'
-        storage.put(new_cred)
+        s.put(new_cred)
 
         access_token = '1/3w'
         token_response = {'access_token': access_token, 'expires_in': 3600}
-        response_content = json.dumps(token_response).encode('utf-8')
-        http = http_mock.HttpMock(data=response_content)
+        http = HttpMockSequence([
+            ({'status': '200'}, json.dumps(token_response).encode('utf-8')),
+        ])
 
-        credentials._refresh(http)
+        credentials._refresh(http.request)
         self.assertEquals(credentials.access_token, access_token)
 
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, credentials.token_uri)
-        self.assertEqual(http.method, 'POST')
-        expected_body = {
-            'grant_type': ['refresh_token'],
-            'client_id': [credentials.client_id],
-            'client_secret': [credentials.client_secret],
-            'refresh_token': [credentials.refresh_token],
-        }
-        self.assertEqual(urllib_parse.parse_qs(http.body), expected_body)
-        expected_headers = {
-            'content-type': 'application/x-www-form-urlencoded',
-            'user-agent': credentials.user_agent,
-        }
-        self.assertEqual(http.headers, expected_headers)
-
     def test_token_refresh_store_expires_soon(self):
         # Tests the case where an access token that is valid when it is read
         # from the store expires before the original request succeeds.
@@ -170,28 +141,28 @@
                       datetime.timedelta(minutes=15))
         credentials = self._create_test_credentials(expiration=expiration)
 
-        storage = file_module.Storage(FILENAME)
-        storage.put(credentials)
-        credentials = storage.get()
+        s = file.Storage(FILENAME)
+        s.put(credentials)
+        credentials = s.get()
         new_cred = copy.copy(credentials)
         new_cred.access_token = 'bar'
-        storage.put(new_cred)
+        s.put(new_cred)
 
         access_token = '1/3w'
         token_response = {'access_token': access_token, 'expires_in': 3600}
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.UNAUTHORIZED},
+        http = HttpMockSequence([
+            ({'status': str(int(http_client.UNAUTHORIZED))},
              b'Initial token expired'),
-            ({'status': http_client.UNAUTHORIZED},
+            ({'status': str(int(http_client.UNAUTHORIZED))},
              b'Store token expired'),
-            ({'status': http_client.OK},
+            ({'status': str(int(http_client.OK))},
              json.dumps(token_response).encode('utf-8')),
-            ({'status': http_client.OK},
+            ({'status': str(int(http_client.OK))},
              b'Valid response to original request')
         ])
 
         credentials.authorize(http)
-        transport.request(http, 'https://example.com')
+        http.request('https://example.com')
         self.assertEqual(credentials.access_token, access_token)
 
     def test_token_refresh_good_store(self):
@@ -199,12 +170,12 @@
                       datetime.timedelta(minutes=15))
         credentials = self._create_test_credentials(expiration=expiration)
 
-        storage = file_module.Storage(FILENAME)
-        storage.put(credentials)
-        credentials = storage.get()
+        s = file.Storage(FILENAME)
+        s.put(credentials)
+        credentials = s.get()
         new_cred = copy.copy(credentials)
         new_cred.access_token = 'bar'
-        storage.put(new_cred)
+        s.put(new_cred)
 
         credentials._refresh(None)
         self.assertEquals(credentials.access_token, 'bar')
@@ -214,43 +185,43 @@
                       datetime.timedelta(minutes=15))
         credentials = self._create_test_credentials(expiration=expiration)
 
-        storage = file_module.Storage(FILENAME)
-        storage.put(credentials)
-        credentials = storage.get()
+        s = file.Storage(FILENAME)
+        s.put(credentials)
+        credentials = s.get()
         new_cred = copy.copy(credentials)
         new_cred.access_token = 'bar'
-        storage.put(new_cred)
+        s.put(new_cred)
 
         valid_access_token = '1/3w'
         token_response = {'access_token': valid_access_token,
                           'expires_in': 3600}
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.UNAUTHORIZED},
+        http = HttpMockSequence([
+            ({'status': str(int(http_client.UNAUTHORIZED))},
              b'Initial token expired'),
-            ({'status': http_client.UNAUTHORIZED},
+            ({'status': str(int(http_client.UNAUTHORIZED))},
              b'Store token expired'),
-            ({'status': http_client.OK},
+            ({'status': str(int(http_client.OK))},
              json.dumps(token_response).encode('utf-8')),
-            ({'status': http_client.OK}, 'echo_request_body')
+            ({'status': str(int(http_client.OK))}, 'echo_request_body')
         ])
 
         body = six.StringIO('streaming body')
 
         credentials.authorize(http)
-        _, content = transport.request(http, 'https://example.com', body=body)
+        _, content = http.request('https://example.com', body=body)
         self.assertEqual(content, 'streaming body')
         self.assertEqual(credentials.access_token, valid_access_token)
 
     def test_credentials_delete(self):
         credentials = self._create_test_credentials()
 
-        storage = file_module.Storage(FILENAME)
-        storage.put(credentials)
-        credentials = storage.get()
-        self.assertIsNotNone(credentials)
-        storage.delete()
-        credentials = storage.get()
-        self.assertIsNone(credentials)
+        s = file.Storage(FILENAME)
+        s.put(credentials)
+        credentials = s.get()
+        self.assertNotEquals(None, credentials)
+        s.delete()
+        credentials = s.get()
+        self.assertEquals(None, credentials)
 
     def test_access_token_credentials(self):
         access_token = 'foo'
@@ -258,11 +229,11 @@
 
         credentials = client.AccessTokenCredentials(access_token, user_agent)
 
-        storage = file_module.Storage(FILENAME)
-        credentials = storage.put(credentials)
-        credentials = storage.get()
+        s = file.Storage(FILENAME)
+        credentials = s.put(credentials)
+        credentials = s.get()
 
-        self.assertIsNotNone(credentials)
+        self.assertNotEquals(None, credentials)
         self.assertEquals('foo', credentials.access_token)
 
         self.assertTrue(os.path.exists(FILENAME))
diff --git a/tests/test_jwt.py b/tests/test_jwt.py
index 6502a4a..ecc58e8 100644
--- a/tests/test_jwt.py
+++ b/tests/test_jwt.py
@@ -17,18 +17,19 @@
 import os
 import tempfile
 import time
-import unittest
 
 import mock
-from six.moves import http_client
+import unittest2
 
 from oauth2client import _helpers
 from oauth2client import client
 from oauth2client import crypt
-from oauth2client import file as file_module
+from oauth2client import file
 from oauth2client import service_account
-from oauth2client import transport
-from tests import http_mock
+from .http_mock import HttpMockSequence
+
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 
 
 _FORMATS_TO_CONSTRUCTOR_ARGS = {
@@ -46,7 +47,7 @@
         return file_obj.read()
 
 
-class CryptTests(unittest.TestCase):
+class CryptTests(unittest2.TestCase):
 
     def setUp(self):
         self.format_ = 'p12'
@@ -113,30 +114,25 @@
         self.assertEqual('billy bob', contents['user'])
         self.assertEqual('data', contents['metadata']['meta'])
 
-    def _verify_http_mock(self, http):
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, client.ID_TOKEN_VERIFICATION_CERTS)
-        self.assertEqual(http.method, 'GET')
-        self.assertIsNone(http.body)
-        self.assertIsNone(http.headers)
-
     def test_verify_id_token_with_certs_uri(self):
         jwt = self._create_signed_jwt()
 
-        http = http_mock.HttpMock(data=datafile('certs.json'))
+        http = HttpMockSequence([
+            ({'status': '200'}, datafile('certs.json')),
+        ])
+
         contents = client.verify_id_token(
             jwt, 'some_audience_address@testing.gserviceaccount.com',
             http=http)
         self.assertEqual('billy bob', contents['user'])
         self.assertEqual('data', contents['metadata']['meta'])
 
-        # Verify mocks.
-        self._verify_http_mock(http)
-
     def test_verify_id_token_with_certs_uri_default_http(self):
         jwt = self._create_signed_jwt()
 
-        http = http_mock.HttpMock(data=datafile('certs.json'))
+        http = HttpMockSequence([
+            ({'status': '200'}, datafile('certs.json')),
+        ])
 
         with mock.patch('oauth2client.transport._CACHED_HTTP', new=http):
             contents = client.verify_id_token(
@@ -145,23 +141,17 @@
         self.assertEqual('billy bob', contents['user'])
         self.assertEqual('data', contents['metadata']['meta'])
 
-        # Verify mocks.
-        self._verify_http_mock(http)
-
     def test_verify_id_token_with_certs_uri_fails(self):
         jwt = self._create_signed_jwt()
         test_email = 'some_audience_address@testing.gserviceaccount.com'
 
-        http = http_mock.HttpMock(
-            headers={'status': http_client.NOT_FOUND},
-            data=datafile('certs.json'))
+        http = HttpMockSequence([
+            ({'status': '404'}, datafile('certs.json')),
+        ])
 
         with self.assertRaises(client.VerifyJwtTokenError):
             client.verify_id_token(jwt, test_email, http=http)
 
-        # Verify mocks.
-        self._verify_http_mock(http)
-
     def test_verify_id_token_bad_tokens(self):
         private_key = datafile('privatekey.' + self.format_)
 
@@ -242,16 +232,12 @@
         self.verifier = crypt.OpenSSLVerifier
 
 
-class SignedJwtAssertionCredentialsTests(unittest.TestCase):
+class SignedJwtAssertionCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
-        self.orig_signer = crypt.Signer
         self.format_ = 'p12'
         crypt.Signer = crypt.OpenSSLSigner
 
-    def tearDown(self):
-        crypt.Signer = self.orig_signer
-
     def _make_credentials(self):
         private_key = datafile('privatekey.' + self.format_)
         signer = crypt.Signer.from_string(private_key)
@@ -271,13 +257,12 @@
 
     def test_credentials_good(self):
         credentials = self._make_credentials()
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK},
-             b'{"access_token":"1/3w","expires_in":3600}'),
-            ({'status': http_client.OK}, 'echo_request_headers'),
+        http = HttpMockSequence([
+            ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
+            ({'status': '200'}, 'echo_request_headers'),
         ])
         http = credentials.authorize(http)
-        resp, content = transport.request(http, 'http://example.org')
+        resp, content = http.request('http://example.org')
         self.assertEqual(b'Bearer 1/3w', content[b'Authorization'])
 
     def test_credentials_to_from_json(self):
@@ -291,16 +276,14 @@
         self.assertEqual(credentials._kwargs, restored._kwargs)
 
     def _credentials_refresh(self, credentials):
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK},
-             b'{"access_token":"1/3w","expires_in":3600}'),
-            ({'status': http_client.UNAUTHORIZED}, b''),
-            ({'status': http_client.OK},
-             b'{"access_token":"3/3w","expires_in":3600}'),
-            ({'status': http_client.OK}, 'echo_request_headers'),
+        http = HttpMockSequence([
+            ({'status': '200'}, b'{"access_token":"1/3w","expires_in":3600}'),
+            ({'status': '401'}, b''),
+            ({'status': '200'}, b'{"access_token":"3/3w","expires_in":3600}'),
+            ({'status': '200'}, 'echo_request_headers'),
         ])
         http = credentials.authorize(http)
-        _, content = transport.request(http, 'http://example.org')
+        _, content = http.request('http://example.org')
         return content
 
     def test_credentials_refresh_without_storage(self):
@@ -313,7 +296,7 @@
 
         filehandle, filename = tempfile.mkstemp()
         os.close(filehandle)
-        store = file_module.Storage(filename)
+        store = file.Storage(filename)
         store.put(credentials)
         credentials.set_store(store)
 
@@ -327,27 +310,19 @@
         SignedJwtAssertionCredentialsTests):
 
     def setUp(self):
-        self.orig_signer = crypt.Signer
         self.format_ = 'pem'
         crypt.Signer = crypt.OpenSSLSigner
 
-    def tearDown(self):
-        crypt.Signer = self.orig_signer
-
 
 class PEMSignedJwtAssertionCredentialsPyCryptoTests(
         SignedJwtAssertionCredentialsTests):
 
     def setUp(self):
-        self.orig_signer = crypt.Signer
         self.format_ = 'pem'
         crypt.Signer = crypt.PyCryptoSigner
 
-    def tearDown(self):
-        crypt.Signer = self.orig_signer
 
-
-class TestHasOpenSSLFlag(unittest.TestCase):
+class TestHasOpenSSLFlag(unittest2.TestCase):
 
     def test_true(self):
         self.assertEqual(True, client.HAS_OPENSSL)
diff --git a/tests/test_service_account.py b/tests/test_service_account.py
index 6756d49..699e699 100644
--- a/tests/test_service_account.py
+++ b/tests/test_service_account.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""oauth2client tests.
+"""Oauth2client tests.
 
 Unit tests for service account credentials implemented using RSA.
 """
@@ -21,18 +21,17 @@
 import json
 import os
 import tempfile
-import unittest
 
+import httplib2
 import mock
 import rsa
-import six
-from six.moves import http_client
+from six import BytesIO
+import unittest2
 
 from oauth2client import client
 from oauth2client import crypt
 from oauth2client import service_account
-from oauth2client import transport
-from tests import http_mock
+from .http_mock import HttpMockSequence
 
 
 def data_filename(filename):
@@ -44,11 +43,9 @@
         return file_obj.read()
 
 
-class ServiceAccountCredentialsTests(unittest.TestCase):
+class ServiceAccountCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
-        self.orig_signer = crypt.Signer
-        self.orig_verifier = crypt.Verifier
         self.client_id = '123'
         self.service_account_email = 'dummy@google.com'
         self.private_key_id = 'ABCDEF'
@@ -62,10 +59,6 @@
             client_id=self.client_id,
         )
 
-    def tearDown(self):
-        crypt.Signer = self.orig_signer
-        crypt.Verifier = self.orig_verifier
-
     def test__to_json_override(self):
         signer = object()
         creds = service_account.ServiceAccountCredentials(
@@ -182,7 +175,7 @@
                 scopes=scopes, token_uri=token_uri, revoke_uri=revoke_uri))
         creds_from_file_contents = (
             service_account.ServiceAccountCredentials.from_p12_keyfile_buffer(
-                service_account_email, six.BytesIO(key_contents),
+                service_account_email, BytesIO(key_contents),
                 private_key_password=private_key_password,
                 scopes=scopes, token_uri=token_uri, revoke_uri=revoke_uri))
         for creds in (creds_from_filename, creds_from_file_contents):
@@ -277,10 +270,10 @@
         utcnow.return_value = NOW
 
         # Create a custom credentials with a mock signer.
-        signer = mock.Mock()
+        signer = mock.MagicMock()
         signed_value = b'signed-content'
-        signer.sign = mock.Mock(name='sign',
-                                return_value=signed_value)
+        signer.sign = mock.MagicMock(name='sign',
+                                     return_value=signed_value)
         credentials = service_account.ServiceAccountCredentials(
             self.service_account_email,
             signer,
@@ -303,10 +296,10 @@
             'access_token': token2,
             'expires_in': lifetime,
         }
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK},
+        http = HttpMockSequence([
+            ({'status': '200'},
              json.dumps(token_response_first).encode('utf-8')),
-            ({'status': http_client.OK},
+            ({'status': '200'},
              json.dumps(token_response_second).encode('utf-8')),
         ])
 
@@ -369,7 +362,6 @@
 
         self.assertEqual(credentials.access_token, token2)
 
-
 TOKEN_LIFE = service_account._JWTAccessCredentials._MAX_TOKEN_LIFETIME_SECS
 T1 = 42
 T1_DATE = datetime.datetime(1970, 1, 1, second=T1)
@@ -387,7 +379,7 @@
 T3_EXPIRY_DATE = T3_DATE + datetime.timedelta(seconds=TOKEN_LIFE)
 
 
-class JWTAccessCredentialsTests(unittest.TestCase):
+class JWTAccessCredentialsTests(unittest2.TestCase):
 
     def setUp(self):
         self.client_id = '123'
@@ -408,15 +400,13 @@
         time.return_value = T1
 
         token_info = self.jwt.get_access_token()
-        certs = {'key': datafile('public_cert.pem')}
         payload = crypt.verify_signed_jwt_with_certs(
-            token_info.access_token, certs, audience=self.url)
-        self.assertEqual(len(payload), 5)
+            token_info.access_token,
+            {'key': datafile('public_cert.pem')}, audience=self.url)
         self.assertEqual(payload['iss'], self.service_account_email)
         self.assertEqual(payload['sub'], self.service_account_email)
         self.assertEqual(payload['iat'], T1)
         self.assertEqual(payload['exp'], T1_EXPIRY)
-        self.assertEqual(payload['aud'], self.url)
         self.assertEqual(token_info.expires_in, T1_EXPIRY - T1)
 
         # Verify that we vend the same token after 100 seconds
@@ -447,20 +437,19 @@
         utcnow.return_value = T1_DATE
         time.return_value = T1
 
-        audience = 'https://test2.url.com'
-        subject = 'dummy2@google.com'
-        claims = {'aud': audience, 'sub': subject}
-        token_info = self.jwt.get_access_token(additional_claims=claims)
-        certs = {'key': datafile('public_cert.pem')}
+        token_info = self.jwt.get_access_token(
+            additional_claims={'aud': 'https://test2.url.com',
+                               'sub': 'dummy2@google.com'
+                               })
         payload = crypt.verify_signed_jwt_with_certs(
-            token_info.access_token, certs, audience=audience)
+            token_info.access_token,
+            {'key': datafile('public_cert.pem')},
+            audience='https://test2.url.com')
         expires_in = token_info.expires_in
-        self.assertEqual(len(payload), 5)
         self.assertEqual(payload['iss'], self.service_account_email)
-        self.assertEqual(payload['sub'], subject)
+        self.assertEqual(payload['sub'], 'dummy2@google.com')
         self.assertEqual(payload['iat'], T1)
         self.assertEqual(payload['exp'], T1_EXPIRY)
-        self.assertEqual(payload['aud'], audience)
         self.assertEqual(expires_in, T1_EXPIRY - T1)
 
     def test_revoke(self):
@@ -485,36 +474,30 @@
         utcnow.return_value = T1_DATE
         time.return_value = T1
 
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b''),
-            ({'status': http_client.OK}, b''),
-        ])
-
-        self.jwt.authorize(http)
-        transport.request(http, self.url)
-
-        # Ensure we use the cached token
-        utcnow.return_value = T2_DATE
-        transport.request(http, self.url)
-
-        # Verify mocks.
-        certs = {'key': datafile('public_cert.pem')}
-        self.assertEqual(len(http.requests), 2)
-        for info in http.requests:
-            self.assertEqual(info['method'], 'GET')
-            self.assertEqual(info['uri'], self.url)
-            self.assertIsNone(info['body'])
-            self.assertEqual(len(info['headers']), 1)
-            bearer, token = info['headers'][b'Authorization'].split()
-            self.assertEqual(bearer, b'Bearer')
+        def mock_request(uri, method='GET', body=None, headers=None,
+                         redirections=0, connection_type=None):
+            self.assertEqual(uri, self.url)
+            bearer, token = headers[b'Authorization'].split()
             payload = crypt.verify_signed_jwt_with_certs(
-                token, certs, audience=self.url)
-            self.assertEqual(len(payload), 5)
+                token,
+                {'key': datafile('public_cert.pem')},
+                audience=self.url)
             self.assertEqual(payload['iss'], self.service_account_email)
             self.assertEqual(payload['sub'], self.service_account_email)
             self.assertEqual(payload['iat'], T1)
             self.assertEqual(payload['exp'], T1_EXPIRY)
-            self.assertEqual(payload['aud'], self.url)
+            self.assertEqual(uri, self.url)
+            self.assertEqual(bearer, b'Bearer')
+            return (httplib2.Response({'status': '200'}), b'')
+
+        h = httplib2.Http()
+        h.request = mock_request
+        self.jwt.authorize(h)
+        h.request(self.url)
+
+        # Ensure we use the cached token
+        utcnow.return_value = T2_DATE
+        h.request(self.url)
 
     @mock.patch('oauth2client.client._UTCNOW')
     @mock.patch('time.time')
@@ -526,126 +509,65 @@
             self.service_account_email, self.signer,
             private_key_id=self.private_key_id, client_id=self.client_id)
 
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b''),
-        ])
+        def mock_request(uri, method='GET', body=None, headers=None,
+                         redirections=0, connection_type=None):
+            self.assertEqual(uri, self.url)
+            bearer, token = headers[b'Authorization'].split()
+            payload = crypt.verify_signed_jwt_with_certs(
+                token,
+                {'key': datafile('public_cert.pem')},
+                audience=self.url)
+            self.assertEqual(payload['iss'], self.service_account_email)
+            self.assertEqual(payload['sub'], self.service_account_email)
+            self.assertEqual(payload['iat'], T1)
+            self.assertEqual(payload['exp'], T1_EXPIRY)
+            self.assertEqual(uri, self.url)
+            self.assertEqual(bearer, b'Bearer')
+            return httplib2.Response({'status': '200'}), b''
 
-        jwt.authorize(http)
-        transport.request(http, self.url)
+        h = httplib2.Http()
+        h.request = mock_request
+        jwt.authorize(h)
+        h.request(self.url)
 
         # Ensure we do not cache the token
         self.assertIsNone(jwt.access_token)
 
-        # Verify mocks.
-        self.assertEqual(len(http.requests), 1)
-        info = http.requests[0]
-        self.assertEqual(info['method'], 'GET')
-        self.assertEqual(info['uri'], self.url)
-        self.assertIsNone(info['body'])
-        self.assertEqual(len(info['headers']), 1)
-        bearer, token = info['headers'][b'Authorization'].split()
-        self.assertEqual(bearer, b'Bearer')
-        certs = {'key': datafile('public_cert.pem')}
-        payload = crypt.verify_signed_jwt_with_certs(
-            token, certs, audience=self.url)
-        self.assertEqual(len(payload), 5)
-        self.assertEqual(payload['iss'], self.service_account_email)
-        self.assertEqual(payload['sub'], self.service_account_email)
-        self.assertEqual(payload['iat'], T1)
-        self.assertEqual(payload['exp'], T1_EXPIRY)
-        self.assertEqual(payload['aud'], self.url)
-
     @mock.patch('oauth2client.client._UTCNOW')
     def test_authorize_stale_token(self, utcnow):
         utcnow.return_value = T1_DATE
         # Create an initial token
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b''),
-            ({'status': http_client.OK}, b''),
-        ])
-        self.jwt.authorize(http)
-        transport.request(http, self.url)
+        h = HttpMockSequence([({'status': '200'}, b''),
+                              ({'status': '200'}, b'')])
+        self.jwt.authorize(h)
+        h.request(self.url)
         token_1 = self.jwt.access_token
 
         # Expire the token
         utcnow.return_value = T3_DATE
-        transport.request(http, self.url)
+        h.request(self.url)
         token_2 = self.jwt.access_token
         self.assertEquals(self.jwt.token_expiry, T3_EXPIRY_DATE)
         self.assertNotEqual(token_1, token_2)
 
-        # Verify mocks.
-        certs = {'key': datafile('public_cert.pem')}
-        self.assertEqual(len(http.requests), 2)
-        issued_at_vals = (T1, T3)
-        exp_vals = (T1_EXPIRY, T3_EXPIRY)
-        for info, issued_at, exp_val in zip(http.requests, issued_at_vals,
-                                            exp_vals):
-            self.assertEqual(info['uri'], self.url)
-            self.assertEqual(info['method'], 'GET')
-            self.assertIsNone(info['body'])
-            self.assertEqual(len(info['headers']), 1)
-            bearer, token = info['headers'][b'Authorization'].split()
-            self.assertEqual(bearer, b'Bearer')
-            # To parse the token, skip the time check, since this
-            # test intentionally has stale tokens.
-            with mock.patch('oauth2client.crypt._verify_time_range',
-                            return_value=True):
-                payload = crypt.verify_signed_jwt_with_certs(
-                    token, certs, audience=self.url)
-            self.assertEqual(len(payload), 5)
-            self.assertEqual(payload['iss'], self.service_account_email)
-            self.assertEqual(payload['sub'], self.service_account_email)
-            self.assertEqual(payload['iat'], issued_at)
-            self.assertEqual(payload['exp'], exp_val)
-            self.assertEqual(payload['aud'], self.url)
-
     @mock.patch('oauth2client.client._UTCNOW')
     def test_authorize_401(self, utcnow):
         utcnow.return_value = T1_DATE
 
-        http = http_mock.HttpMockSequence([
-            ({'status': http_client.OK}, b''),
-            ({'status': http_client.UNAUTHORIZED}, b''),
-            ({'status': http_client.OK}, b''),
-        ])
-        self.jwt.authorize(http)
-        transport.request(http, self.url)
+        h = HttpMockSequence([
+            ({'status': '200'}, b''),
+            ({'status': '401'}, b''),
+            ({'status': '200'}, b'')])
+        self.jwt.authorize(h)
+        h.request(self.url)
         token_1 = self.jwt.access_token
 
         utcnow.return_value = T2_DATE
-        response, _ = transport.request(http, self.url)
-        self.assertEquals(response.status, http_client.OK)
+        self.assertEquals(h.request(self.url)[0].status, 200)
         token_2 = self.jwt.access_token
         # Check the 401 forced a new token
         self.assertNotEqual(token_1, token_2)
 
-        # Verify mocks.
-        certs = {'key': datafile('public_cert.pem')}
-        self.assertEqual(len(http.requests), 3)
-        issued_at_vals = (T1, T1, T2)
-        exp_vals = (T1_EXPIRY, T1_EXPIRY, T2_EXPIRY)
-        for info, issued_at, exp_val in zip(http.requests, issued_at_vals,
-                                            exp_vals):
-            self.assertEqual(info['uri'], self.url)
-            self.assertEqual(info['method'], 'GET')
-            self.assertIsNone(info['body'])
-            self.assertEqual(len(info['headers']), 1)
-            bearer, token = info['headers'][b'Authorization'].split()
-            self.assertEqual(bearer, b'Bearer')
-            # To parse the token, skip the time check, since this
-            # test intentionally has stale tokens.
-            with mock.patch('oauth2client.crypt._verify_time_range',
-                            return_value=True):
-                payload = crypt.verify_signed_jwt_with_certs(
-                    token, certs, audience=self.url)
-            self.assertEqual(len(payload), 5)
-            self.assertEqual(payload['iss'], self.service_account_email)
-            self.assertEqual(payload['sub'], self.service_account_email)
-            self.assertEqual(payload['iat'], issued_at)
-            self.assertEqual(payload['exp'], exp_val)
-            self.assertEqual(payload['aud'], self.url)
-
     @mock.patch('oauth2client.client._UTCNOW')
     def test_refresh(self, utcnow):
         utcnow.return_value = T1_DATE
diff --git a/tests/test_tools.py b/tests/test_tools.py
index 52191f0..369f567 100644
--- a/tests/test_tools.py
+++ b/tests/test_tools.py
@@ -12,20 +12,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import argparse
 import socket
 import sys
 import threading
-import unittest
 
 import mock
 from six.moves.urllib import request
+import unittest2
 
 from oauth2client import client
 from oauth2client import tools
 
+try:
+    import argparse
+except ImportError:  # pragma: NO COVER
+    raise unittest2.SkipTest('argparase unavailable.')
 
-class TestClientRedirectServer(unittest.TestCase):
+
+class TestClientRedirectServer(unittest2.TestCase):
     """Test the ClientRedirectServer and ClientRedirectHandler classes."""
 
     def test_ClientRedirectServer(self):
@@ -47,7 +51,7 @@
         self.assertEqual(httpd.query_params.get('code'), code)
 
 
-class TestRunFlow(unittest.TestCase):
+class TestRunFlow(unittest2.TestCase):
 
     def setUp(self):
         self.server = mock.Mock()
@@ -183,6 +187,6 @@
         self.assertFalse(self.server.handle_request.called)
 
 
-class TestMessageIfMissing(unittest.TestCase):
+class TestMessageIfMissing(unittest2.TestCase):
     def test_message_if_missing(self):
         self.assertIn('somefile.txt', tools.message_if_missing('somefile.txt'))
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 2884200..e9782a8 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -12,17 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest
-
 import httplib2
 import mock
+import unittest2
 
 from oauth2client import client
 from oauth2client import transport
-from tests import http_mock
 
 
-class TestMemoryCache(unittest.TestCase):
+class TestMemoryCache(unittest2.TestCase):
 
     def test_get_set_delete(self):
         cache = transport.MemoryCache()
@@ -34,7 +32,7 @@
         self.assertIsNone(cache.get('foo'))
 
 
-class Test_get_cached_http(unittest.TestCase):
+class Test_get_cached_http(unittest2.TestCase):
 
     def test_global(self):
         cached_http = transport.get_cached_http()
@@ -48,22 +46,15 @@
         self.assertIs(result, cache)
 
 
-class Test_get_http_object(unittest.TestCase):
+class Test_get_http_object(unittest2.TestCase):
 
     @mock.patch.object(httplib2, 'Http', return_value=object())
     def test_it(self, http_klass):
         result = transport.get_http_object()
         self.assertEqual(result, http_klass.return_value)
-        http_klass.assert_called_once_with()
-
-    @mock.patch.object(httplib2, 'Http', return_value=object())
-    def test_with_args(self, http_klass):
-        result = transport.get_http_object(1, 2, foo='bar')
-        self.assertEqual(result, http_klass.return_value)
-        http_klass.assert_called_once_with(1, 2, foo='bar')
 
 
-class Test__initialize_headers(unittest.TestCase):
+class Test__initialize_headers(unittest2.TestCase):
 
     def test_null(self):
         result = transport._initialize_headers(None)
@@ -76,7 +67,7 @@
         self.assertIsNot(result, headers)
 
 
-class Test__apply_user_agent(unittest.TestCase):
+class Test__apply_user_agent(unittest2.TestCase):
 
     def test_null(self):
         headers = object()
@@ -100,7 +91,7 @@
         self.assertEqual(result, {'user-agent': final_agent})
 
 
-class Test_clean_headers(unittest.TestCase):
+class Test_clean_headers(unittest2.TestCase):
 
     def test_no_modify(self):
         headers = {b'key': b'val'}
@@ -128,7 +119,7 @@
         self.assertEqual(result, header_str)
 
 
-class Test_wrap_http_for_auth(unittest.TestCase):
+class Test_wrap_http_for_auth(unittest2.TestCase):
 
     def test_wrap(self):
         credentials = object()
@@ -138,45 +129,3 @@
         self.assertIsNone(result)
         self.assertNotEqual(http.request, orig_req_method)
         self.assertIs(http.request.credentials, credentials)
-
-
-class Test_request(unittest.TestCase):
-
-    uri = 'http://localhost'
-    method = 'POST'
-    body = 'abc'
-    redirections = 3
-
-    def test_with_request_attr(self):
-        mock_result = object()
-        headers = {'foo': 'bar'}
-        http = http_mock.HttpMock(headers=headers, data=mock_result)
-
-        response, content = transport.request(
-            http, self.uri, method=self.method, body=self.body,
-            redirections=self.redirections)
-        self.assertEqual(response, headers)
-        self.assertIs(content, mock_result)
-        # Verify mocks.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, self.uri)
-        self.assertEqual(http.method, self.method)
-        self.assertEqual(http.body, self.body)
-        self.assertIsNone(http.headers)
-
-    def test_with_callable_http(self):
-        headers = {}
-        mock_result = object()
-        http = http_mock.HttpMock(headers=headers, data=mock_result)
-
-        result = transport.request(http, self.uri, method=self.method,
-                                   body=self.body,
-                                   redirections=self.redirections)
-        self.assertEqual(result, (headers, mock_result))
-        # Verify mock.
-        self.assertEqual(http.requests, 1)
-        self.assertEqual(http.uri, self.uri)
-        self.assertEqual(http.method, self.method)
-        self.assertEqual(http.body, self.body)
-        self.assertIsNone(http.headers)
-        self.assertEqual(http.redirections, self.redirections)
diff --git a/tests/test_util.py b/tests/test_util.py
new file mode 100644
index 0000000..533460f
--- /dev/null
+++ b/tests/test_util.py
@@ -0,0 +1,122 @@
+"""Unit tests for oauth2client.util."""
+
+import mock
+import unittest2
+
+from oauth2client import util
+
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+
+class PositionalTests(unittest2.TestCase):
+
+    def test_usage(self):
+        util.positional_parameters_enforcement = util.POSITIONAL_EXCEPTION
+
+        # 1 positional arg, 1 keyword-only arg.
+        @util.positional(1)
+        def fn(pos, kwonly=None):
+            return True
+
+        self.assertTrue(fn(1))
+        self.assertTrue(fn(1, kwonly=2))
+        with self.assertRaises(TypeError):
+            fn(1, 2)
+
+        # No positional, but a required keyword arg.
+        @util.positional(0)
+        def fn2(required_kw):
+            return True
+
+        self.assertTrue(fn2(required_kw=1))
+        with self.assertRaises(TypeError):
+            fn2(1)
+
+        # Unspecified positional, should automatically figure out 1 positional
+        # 1 keyword-only (same as first case above).
+        @util.positional
+        def fn3(pos, kwonly=None):
+            return True
+
+        self.assertTrue(fn3(1))
+        self.assertTrue(fn3(1, kwonly=2))
+        with self.assertRaises(TypeError):
+            fn3(1, 2)
+
+    @mock.patch('oauth2client.util.logger')
+    def test_enforcement_warning(self, mock_logger):
+        util.positional_parameters_enforcement = util.POSITIONAL_WARNING
+
+        @util.positional(1)
+        def fn(pos, kwonly=None):
+            return True
+
+        self.assertTrue(fn(1, 2))
+        self.assertTrue(mock_logger.warning.called)
+
+    @mock.patch('oauth2client.util.logger')
+    def test_enforcement_ignore(self, mock_logger):
+        util.positional_parameters_enforcement = util.POSITIONAL_IGNORE
+
+        @util.positional(1)
+        def fn(pos, kwonly=None):
+            return True
+
+        self.assertTrue(fn(1, 2))
+        self.assertFalse(mock_logger.warning.called)
+
+
+class ScopeToStringTests(unittest2.TestCase):
+
+    def test_iterables(self):
+        cases = [
+            ('', ''),
+            ('', ()),
+            ('', []),
+            ('', ('',)),
+            ('', ['', ]),
+            ('a', ('a',)),
+            ('b', ['b', ]),
+            ('a b', ['a', 'b']),
+            ('a b', ('a', 'b')),
+            ('a b', 'a b'),
+            ('a b', (s for s in ['a', 'b'])),
+        ]
+        for expected, case in cases:
+            self.assertEqual(expected, util.scopes_to_string(case))
+
+
+class StringToScopeTests(unittest2.TestCase):
+
+    def test_conversion(self):
+        cases = [
+            (['a', 'b'], ['a', 'b']),
+            ('', []),
+            ('a', ['a']),
+            ('a b c d e f', ['a', 'b', 'c', 'd', 'e', 'f']),
+        ]
+
+        for case, expected in cases:
+            self.assertEqual(expected, util.string_to_scopes(case))
+
+
+class AddQueryParameterTests(unittest2.TestCase):
+
+    def test__add_query_parameter(self):
+        self.assertEqual(
+            util._add_query_parameter('/action', 'a', None),
+            '/action')
+        self.assertEqual(
+            util._add_query_parameter('/action', 'a', 'b'),
+            '/action?a=b')
+        self.assertEqual(
+            util._add_query_parameter('/action?a=b', 'a', 'c'),
+            '/action?a=c')
+        # Order is non-deterministic.
+        self.assertIn(
+            util._add_query_parameter('/action?a=b', 'c', 'd'),
+            ['/action?a=b&c=d', '/action?c=d&a=b'])
+        self.assertEqual(
+            util._add_query_parameter('/action', 'a', ' ='),
+            '/action?a=+%3D')
diff --git a/tox.ini b/tox.ini
index d725881..b0781a8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = flake8,py27,py34,py35,gae,cover
+envlist = py26,py27,py33,py34,py35,pypy,gae,cover
 
 [testenv]
 basedeps = mock>=1.3.0
@@ -7,44 +7,99 @@
            cryptography>=1.0
            pyopenssl>=0.14
            webtest
-           pytest
+           nose
            flask
+           unittest2
            sqlalchemy
            fasteners
 deps = {[testenv]basedeps}
        django
        keyring
-       jsonpickle
 setenv =
     pypy: with_gmp=no
     DJANGO_SETTINGS_MODULE=tests.contrib.django_util.settings
-commands =
-    py.test {posargs}
+commands = nosetests --ignore-files=test_appengine\.py --ignore-files=test__appengine_ndb\.py {posargs}
 
 [coverbase]
 basepython = python2.7
 commands =
-    py.test \
-      --cov=oauth2client \
-      --cov=tests
-    py.test \
-      --cov=oauth2client \
-      --cov=tests \
-      --cov-append \
-       --gae-sdk={env:GAE_PYTHONPATH:} \
-      tests/contrib/appengine
+    nosetests \
+      --with-coverage \
+      --cover-package=oauth2client \
+      --cover-package=tests \
+      --cover-erase \
+      --cover-tests \
+      --cover-branches \
+      --ignore-files=test_appengine\.py \
+      --ignore-files=test__appengine_ndb\.py
+    nosetests \
+      --with-coverage \
+      --cover-package=oauth2client.contrib.appengine \
+      --cover-package=oauth2client.contrib._appengine_ndb \
+      --cover-package=tests.contrib.test_appengine \
+      --cover-package=tests.contrib.test__appengine_ndb \
+      --with-gae \
+      --cover-tests \
+      --cover-branches \
+      --gae-application=tests/data \
+      --gae-lib-root={env:GAE_PYTHONPATH:google_appengine} \
+      --logging-level=INFO \
+      tests/contrib/test_appengine.py \
+      tests/contrib/test__appengine_ndb.py
 deps = {[testenv]deps}
     coverage
-    pytest-cov
+    nosegae
+
+[testenv:py26]
+basepython =
+    python2.6
+commands =
+    nosetests \
+      --ignore-files=test_appengine\.py \
+      --ignore-files=test__appengine_ndb\.py \
+      --ignore-files=test_keyring_storage\.py \
+      --exclude-dir=oauth2client/contrib/django_util \
+      --exclude-dir=tests/contrib/django_util \
+      {posargs}
+deps = {[testenv]basedeps}
+       nose-exclude
+
+[testenv:py33]
+basepython =
+    python3.3
+commands =
+    nosetests \
+      --ignore-files=test_appengine\.py \
+      --ignore-files=test__appengine_ndb\.py \
+      --ignore-files=test_django_orm\.py \
+      --ignore-files=test_django_settings\.py \
+      --ignore-files=test_django_util\.py \
+      --exclude-dir=oauth2client/contrib/django_util \
+      --exclude-dir=tests/contrib/django_util \
+      {posargs}
+deps = {[testenv]basedeps}
+       keyring
+       nose-exclude
 
 [testenv:cover]
 basepython = {[coverbase]basepython}
 commands =
     {[coverbase]commands}
-    coverage report --show-missing --fail-under=100
+    coverage report --show-missing --cover-min-percentage=100
 deps =
     {[coverbase]deps}
 
+[testenv:coveralls]
+basepython = {[coverbase]basepython}
+commands =
+    {[coverbase]commands}
+    coverage report --show-missing
+    coveralls
+deps =
+    {[coverbase]deps}
+    coveralls
+passenv = {[testenv:system-tests]passenv}
+
 [testenv:docs]
 basepython = python2.7
 deps =
@@ -59,8 +114,15 @@
 [testenv:gae]
 basepython = python2.7
 deps = {[testenv]basedeps}
+       nosegae
 commands =
-    py.test --gae-sdk={env:GAE_PYTHONPATH:} tests/contrib/appengine
+    nosetests \
+      --with-gae \
+      --gae-lib-root={env:GAE_PYTHONPATH:google_appengine} \
+      --gae-application=tests/data \
+      --logging-level=INFO \
+      tests/contrib/test_appengine.py \
+      tests/contrib/test__appengine_ndb.py
 
 [testenv:system-tests]
 basepython =
@@ -71,7 +133,7 @@
     pycrypto>=2.6
     cryptography>=1.0
     pyopenssl>=0.14
-passenv = GOOGLE_* OAUTH2CLIENT_* TRAVIS* encrypted_*
+passenv = GOOGLE_* OAUTH2CLIENT_* TRAVIS*
 
 [testenv:system-tests3]
 basepython =
@@ -91,6 +153,7 @@
     python {toxinidir}/scripts/run_gce_system_tests.py
 deps =
     pycrypto>=2.6
+    unittest2
 passenv = {[testenv:system-tests]passenv}
 
 [testenv:flake8]
@@ -100,17 +163,16 @@
     flake8-import-order
 
 [flake8]
-exclude = .tox,.git,./*.egg,build,.cache,env,__pycache__
-application-import-names = oauth2client, tests
+exclude = .tox,.git,./*.egg,build,
+application-import-names = oauth2client
 putty-ignore =
   # E402 module level import not at top of file
-  # This file has needed configurations defined before import
+  # These files have needed configurations defined before import
   docs/conf.py : E402
+  tests/contrib/test_appengine.py : E402
+  # Additionally, ignore E100 (imports in wrong order) for Django configuration
+  tests/contrib/test_django_orm.py : E402,I100
   # E501 line too long
   # Ignore lines over 80 chars that include "http:" or "https:"
   /http:/ : E501
   /https:/ : E501
-  # E722 do not use bare except
-  # Existing sloppy usages.
-  oauth2client/crypt.py : E722
-  oauth2client/contrib/multiprocess_file_storage.py : E722