[rasterio] 02/07: Imported Upstream version 0.35.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Wed May 4 07:34:54 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository rasterio.

commit cf4a57723c4d340d5fdf7c7fffecb2512a84f055
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Wed May 4 09:06:10 2016 +0200

    Imported Upstream version 0.35.0
---
 .travis.yml                                      |   5 +-
 AUTHORS.txt                                      |   1 +
 CHANGES.txt                                      |  26 +++-
 README.rst                                       | 130 +++++-----------
 cloudformation/testuser.json                     |  64 ++++++++
 docs/color.rst                                   |   2 +-
 docs/concurrency.rst                             |   6 +-
 docs/configuration.rst                           |  88 +++++++++++
 docs/cookbook.rst                                |  39 +++--
 docs/data_model.rst                              |   5 +-
 docs/features.rst                                |   2 +-
 docs/image_options.rst                           |  55 +++++--
 docs/img/green_box_kmz.png                       | Bin 0 -> 1452013 bytes
 docs/index.rst                                   |   2 +
 docs/installation.rst                            |  10 ++
 docs/osgeo_gdal_migration.rst                    |   6 +-
 docs/rasterio.rst                                |  12 ++
 docs/{recipies => recipes}/band_summary_stats.py |   0
 docs/{recipies => recipes}/filter.py             |   0
 docs/{recipies => recipes}/mask_shp.py           |   0
 docs/recipes/raster_to_kmz.py                    |  57 +++++++
 docs/{recipies => recipes}/reproject.py          |   0
 docs/{recipies => recipes}/saturation.py         |   0
 docs/reproject.rst                               |   2 +-
 docs/resampling.rst                              | 110 ++++++++++++-
 docs/windowed-rw.rst                             |  27 ++--
 docs/working_with_datasets.rst                   |  28 +++-
 docs/writing.rst                                 |   1 -
 examples/async-rasterio.py                       |   4 +-
 examples/concurrent-cpu-bound.py                 |   7 +-
 examples/decimate.py                             |   3 +-
 examples/rasterize_geometry.py                   |  10 +-
 examples/reproject.py                            |   2 +-
 examples/sieve.py                                |   2 +-
 examples/total.py                                |   2 +-
 rasterio/__init__.py                             | 162 +++++++++++--------
 rasterio/_base.pxd                               |   1 -
 rasterio/_base.pyx                               |  54 +++----
 rasterio/_copy.pyx                               |   9 +-
 rasterio/_drivers.pyx                            | 145 +++++++++--------
 rasterio/_features.pyx                           |  11 +-
 rasterio/_fill.pyx                               |   4 +-
 rasterio/_io.pyx                                 |  68 ++++----
 rasterio/_warp.pyx                               |  27 +---
 rasterio/aws.py                                  |  61 --------
 rasterio/coords.py                               |   9 +-
 rasterio/crs.py                                  |  42 +++--
 rasterio/dtypes.py                               |  45 +++---
 rasterio/enums.py                                | 116 ++++++++------
 rasterio/env.py                                  | 188 ++++++++++++++++++++++
 rasterio/errors.py                               |  17 +-
 rasterio/features.py                             |  70 ++++-----
 rasterio/fill.py                                 |  13 +-
 rasterio/five.py                                 |   9 +-
 rasterio/mask.py                                 |   6 +-
 rasterio/merge.py                                |  28 ++--
 rasterio/plot.py                                 |  28 ++--
 rasterio/profiles.py                             |   5 +-
 rasterio/rio/bounds.py                           |  12 +-
 rasterio/rio/calc.py                             |  16 +-
 rasterio/rio/clip.py                             |   4 +-
 rasterio/rio/convert.py                          |   4 +-
 rasterio/rio/edit_info.py                        |   4 +-
 rasterio/rio/env.py                              |   4 +-
 rasterio/rio/info.py                             |  10 +-
 rasterio/rio/insp.py                             |  12 +-
 rasterio/rio/main.py                             |  26 +---
 rasterio/rio/mask.py                             |   8 +-
 rasterio/rio/merge.py                            |  29 ++--
 rasterio/rio/overview.py                         |  16 +-
 rasterio/rio/rasterize.py                        |   8 +-
 rasterio/rio/sample.py                           |   4 +-
 rasterio/rio/shapes.py                           |  13 +-
 rasterio/rio/stack.py                            |  14 +-
 rasterio/rio/transform.py                        |   5 +-
 rasterio/rio/warp.py                             |  18 ++-
 rasterio/tool.py                                 |  17 +-
 rasterio/transform.py                            |  27 +++-
 rasterio/vfs.py                                  |  24 +--
 rasterio/warnings.py                             |   9 --
 rasterio/warp.py                                 |  62 +++++---
 rasterio/windows.py                              |  43 +++--
 requirements-dev.txt                             |   4 +-
 requirements.txt                                 |   3 +-
 scripts/travis_gdal_install.sh                   |   6 +-
 setup.py                                         |  31 ++--
 tests/README.rst                                 |  20 +++
 tests/conftest.py                                |  33 ++--
 tests/data/alpha.tif                             | Bin 0 -> 105793 bytes
 tests/data/world.rgb.tif                         | Bin 0 -> 411822 bytes
 tests/test_aws.py                                | 135 ----------------
 tests/test_band.py                               |   7 +-
 tests/test_band_masks.py                         |  24 ++-
 tests/test_blocks.py                             |   6 +-
 tests/test_colorinterp.py                        |  44 +++---
 tests/test_colormap.py                           |  59 +++----
 tests/test_crs.py                                |  31 ++--
 tests/test_deprecations.py                       |  85 +++++++++-
 tests/test_driver_management.py                  |  73 +++++----
 tests/test_env.py                                | 190 +++++++++++++++++++++++
 tests/test_err.py                                |   8 +-
 tests/test_features.py                           |  87 ++++++-----
 tests/test_indexing.py                           | 113 ++++++++++----
 tests/test_mask_creation.py                      |  14 +-
 tests/test_nodata.py                             |  26 ++--
 tests/test_open.py                               |  22 +++
 tests/test_overviews.py                          |  21 ++-
 tests/test_png.py                                |   2 +-
 tests/test_read.py                               |  18 +--
 tests/test_read_boundless.py                     |  15 ++
 tests/test_read_resample.py                      |  12 ++
 tests/test_revolvingdoor.py                      |   4 +-
 tests/test_rio_convert.py                        |  13 +-
 tests/test_rio_features.py                       |  65 +++++++-
 tests/test_rio_info.py                           |   7 +
 tests/test_rio_merge.py                          | 121 ++++++++-------
 tests/test_rio_overview.py                       |   3 +-
 tests/test_rio_stack.py                          |   1 +
 tests/test_rio_warp.py                           |  71 ++++++++-
 tests/test_tool.py                               |  64 ++++----
 tests/test_transform.py                          |   9 ++
 tests/test_update.py                             |  25 ++-
 tests/test_vfs.py                                |  10 +-
 tests/test_warnings.py                           |   2 +-
 tests/test_warp.py                               |  88 ++++++-----
 tests/test_warp_transform.py                     |  13 +-
 tests/test_write.py                              | 144 ++++++++---------
 127 files changed, 2477 insertions(+), 1467 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 341675c..30d0c12 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,8 +10,8 @@ env:
     - PIP_FIND_LINKS=file://$HOME/.cache/pip/wheels
     - GDALINST=$HOME/gdalinstall
     - GDALBUILD=$HOME/gdalbuild
-    - secure: "g2h6k0oCsLnYVor3qYSUdLVbifvkkbVqrsiYZ3NZpZKYUc2bveUuCRdqVIU5oGmoOFh8rI1thmtu5QKbbtloP779uI+N4RSBBrp+UmiAryb7wnBFCVAEUrWAKrYOWjcVm5y0ShEIHwtFLsarWoU14WXVZo7rSaAQ03oTke6KvC0="
-    - secure: "XV9rt4WOIO9eZfpy1yuXQv93IDj1rrIn+d/WM0QRn7D6vBeVyahmowvMlJDzpeQqiY7FC5AC/K9OM/lGHFmu9Y0nzMf80GA3Gd543vYKYtxGLFWqggKH1Kw+s65nTikS2seZMzk6j976UcgnBRdjqwuKdk/aqTOGFiYqS8Zs6SE="
+    - secure: "hvRtEFBJ7qoRNxM+xCcSp4RAXMIRhllFtXZbEAs8xdz6ov3DHBB16QKXLIQ713R9gQHsoyZkEShLSU22pqD/37EqdFtGQ7V6iICfVZMzUp4YLxbw2JClT4MHmjTzbtrkh4LvxpUS2e+sZUukFisfYiNgklDyaNPTl15f0YFTr18="
+    - secure: "xYB0j68QKkKTHa2fdVTr8SAmge8FITZCLyIcdIVdc0PcowEHXEgYcGScXYgWUarQrpfshBlf7xV8jRMzLSWj8ut5k52ozRunm1tjSYhHrROQhZIntfuFgEkF7N05o144XslrrESh6L0q6scFMPvOOepHRMbiApYLhTHRAhHFp7Q="
   matrix:
     - GDALVERSION="1.9.2"
     - GDALVERSION="1.10.0"  # doesn't exist but path/lib will fall back to system
@@ -33,6 +33,7 @@ addons:
 python:
   - "2.7"
   - "3.4"
+  - "3.5"
 before_install:
   - pip install -U pip
   - pip install wheel
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 6526ac6..5eaf700 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -8,6 +8,7 @@ Authors
 * AsgerPetersen <asgerpetersen at gmail.com>
 * Bas Couwenberg <sebastic at xs4all.nl>
 * Brendan Ward <bcward at consbio.org>
+* Erik Seglem <erik.seglem at gmail.com>
 * Etienne B. Racine <etiennebr at gmail.com>
 * Even Rouault <even.rouault at spatialys.com>
 * Jacques Tardie <hi at jacquestardie.org>
diff --git a/CHANGES.txt b/CHANGES.txt
index 4e1f459..4a76f88 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,9 +1,33 @@
 Changes
 =======
 
-0.34.0 (2016-04-04)
+0.35.0 (2016-05-04)
 -------------------
+- Requirements: affine requirement upped to >=1.3.0 (#430).
+- Bug fix: passing an empty JSON object to `crs.from_string()` raises CRSError
+  instead of passing silently (#628, #642).
+- Bug fix: GDAL errors are no longer written to stderr; we no longer undefine
+  error handlers (#649, #658).
+- Bug fix: the Rasterio library only configures a NullHandler, applications
+  must configure their own handlers to see Rasterio's log messages (#649,
+  #658).
+- Bug fix: AWS credentials are only sought by Rasterio when s3:// URLs are
+  passed to `rasterio.open()` (#650, #665).
+- Bug fix: window comparison functions now raise a ValueError when windows do
+  not intersect instead of returning an empty sequence (#651, #656, #662).
+- Bug fix: upgrade from deprecated Numpy usage in `read()` by explicitly
+  converting window offsets to ints (#678, #680).
+- Refactoring: window comparison functions may now take a variable number of
+  windows as positional arguments in addition to a sequence of windows.
+- Refactoring: logging is much finer grained now because we've changed to the
+  `logger = logging.getLogger(__name__)` pattern throughout Rasterio (#649,
+  #658).
+- Refactoring: replaced old `drivers()` implementation with a new `Env` class
+  and more consistent usage of it through the library and command line
+  interface (#665, #682).
 
+0.34.0 (2016-04-04)
+-------------------
 - Bug fix: S3 support was found missing in several of the CLI commands 
   mentioned below. This is corrected in 0.34 and we have the tests to prove it
   (#633).
diff --git a/README.rst b/README.rst
index eaac96c..ef74520 100644
--- a/README.rst
+++ b/README.rst
@@ -27,42 +27,31 @@ band.  This new band is then written to a new single band TIFF.
 
     import numpy
     import rasterio
-    import subprocess
-
-    # Register GDAL format drivers and configuration options with a
-    # context manager.
-    with rasterio.drivers():
-
-        # Read raster bands directly to Numpy arrays.
-        #
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            r, g, b = src.read()
-
-        # Combine arrays in place. Expecting that the sum will
-        # temporarily exceed the 8-bit integer range, initialize it as
-        # a 64-bit float (the numpy default) array. Adding other
-        # arrays to it in-place converts those arrays "up" and
-        # preserves the type of the total array.
-        total = numpy.zeros(r.shape)
-        for band in r, g, b:
-            total += band
-        total /= 3
-
-        # Write the product as a raster band to a new 8-bit file. For
-        # the new file's profile, we start with the meta attributes of
-        # the source file, but then change the band count to 1, set the
-        # dtype to uint8, and specify LZW compression.
-        profile = src.profile
-        profile.update(
-            dtype=rasterio.uint8,
-            count=1,
-            compress='lzw')
-
-        with rasterio.open('example-total.tif', 'w', **profile) as dst:
-            dst.write(total.astype(rasterio.uint8), 1)
-
-    # At the end of the ``with rasterio.drivers()`` block, context
-    # manager exits and all drivers are de-registered.
+
+    # Read raster bands directly to Numpy arrays.
+    #
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        r, g, b = src.read()
+
+    # Combine arrays in place. Expecting that the sum will
+    # temporarily exceed the 8-bit integer range, initialize it as
+    # a 64-bit float (the numpy default) array. Adding other
+    # arrays to it in-place converts those arrays "up" and
+    # preserves the type of the total array.
+    total = numpy.zeros(r.shape)
+    for band in r, g, b:
+        total += band
+    total /= 3
+
+    # Write the product as a raster band to a new 8-bit file. For
+    # the new file's profile, we start with the meta attributes of
+    # the source file, but then change the band count to 1, set the
+    # dtype to uint8, and specify LZW compression.
+    profile = src.profile
+    profile.update(dtype=rasterio.uint8, count=1, compress='lzw')
+
+    with rasterio.open('example-total.tif', 'w', **profile) as dst:
+        dst.write(total.astype(rasterio.uint8), 1)
 
 The output:
 
@@ -77,15 +66,14 @@ Simple access is provided to properties of a geospatial raster file.
 
 .. code-block:: python
 
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            print(src.width, src.height)
-            print(src.crs)
-            print(src.affine)
-            print(src.count)
-            print(src.indexes)
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        print(src.width, src.height)
+        print(src.crs)
+        print(src.affine)
+        print(src.count)
+        print(src.indexes)
 
-    # Output:
+    # Printed:
     # (791, 718)
     # {u'units': u'm', u'no_defs': True, u'ellps': u'WGS84', u'proj': u'utm', u'zone': 18}
     # Affine(300.0379266750948, 0.0, 101985.0,
@@ -98,11 +86,11 @@ georeferenced coordinates and vice versa.
 
 
 .. code-block:: python
-    
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            print src.window(**src.window_bounds(((100, 200), (100, 200))))
-    # Output:
+
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        print src.window(**src.window_bounds(((100, 200), (100, 200))))
+
+    # Printed:
     # ((100, 200), (100, 200))
 
 Rasterio CLI
@@ -281,44 +269,10 @@ From the repo directory, run py.test
 
 .. code-block:: console
 
-    $ py.test
+    $ python -m pytest
 
 Note: some tests do not succeed on Windows (see
-`#66
-<https://github.com/mapbox/rasterio/issues/66>`__.).
-
-
-Downstream testing
-------------------
-
-If your project depends on Rasterio and uses Travis-CI, you can speed up your
-builds by fetching Rasterio and its dependencies as a set of wheels from 
-GitHub as done in `rio-plugin-example 
-<https://github.com/sgillies/rio-plugin-example/blob/master/.travis.yml>`__.
-
-.. code-block:: yaml
-
-    language: python
-    env:
-      - RASTERIO_VERSION=0.26
-    python:
-      - "2.7"
-      - "3.4"
-    cache:
-      directories:
-        - $HOME/.pip-cache/
-        - $HOME/wheelhouse
-    before_install:
-      - sudo add-apt-repository -y ppa:ubuntugis/ppa
-      - sudo apt-get update -qq
-      - sudo apt-get install -y libgdal1h gdal-bin
-      - curl -L https://github.com/mapbox/rasterio/releases/download/$RASTERIO_VERSION/rasterio-travis-wheels-$TRAVIS_PYTHON_VERSION.tar.gz > /tmp/wheelhouse.tar.gz
-      - tar -xzvf /tmp/wheelhouse.tar.gz -C $HOME
-    install:
-      - pip install --use-wheel --find-links=$HOME/wheelhouse -e .[test] --cache-dir $HOME/.pip-cache
-    script: 
-      - py.test
-
+`#66 <https://github.com/mapbox/rasterio/issues/66>`__.).
 
 Documentation
 -------------
@@ -328,14 +282,14 @@ See https://github.com/mapbox/rasterio/tree/master/docs.
 License
 -------
 
-See LICENSE.txt
+See LICENSE.txt.
 
 Authors
 -------
 
-See AUTHORS.txt
+See AUTHORS.txt.
 
 Changes
 -------
 
-See CHANGES.txt
+See CHANGES.txt.
diff --git a/cloudformation/testuser.json b/cloudformation/testuser.json
new file mode 100644
index 0000000..7869cdd
--- /dev/null
+++ b/cloudformation/testuser.json
@@ -0,0 +1,64 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "Rasterio Testing Resources",
+    "Resources": {
+        "testuser": {
+            "Type": "AWS::IAM::User",
+            "Properties": {
+                "Path": "/",
+                "Policies": [
+                    {
+                        "PolicyName": "testpolicy",
+                        "PolicyDocument": {
+                            "Statement": [
+                                {
+                                    "Resource": [
+                                        "arn:aws:s3:::landsat-pds",
+                                        "arn:aws:s3:::landsat-pds/*"
+                                    ],
+                                    "Action": [
+                                        "s3:ListBucket"
+                                    ],
+                                    "Effect": "Allow"
+                                },
+                                {
+                                    "Resource": [
+                                        "arn:aws:s3:::landsat-pds/*"
+                                    ],
+                                    "Action": [
+                                        "s3:GetObject"
+                                    ],
+                                    "Effect": "Allow"
+                                }
+                            ]
+                        }
+                    }
+                ]
+            }
+        },
+        "TestKey": {
+            "Type": "AWS::IAM::AccessKey",
+            "Properties": {
+                "UserName": {
+                    "Ref": "testuser"
+                }
+            }
+        }
+    },
+    "Outputs": {
+        "TestAccessKeyId": {
+            "Value": {
+                "Ref": "TestKey"
+            }
+        },
+        "TestSecretAccessKey": {
+            "Value": {
+                "Fn::GetAtt": [
+                    "TestKey",
+                    "SecretAccessKey"
+                ]
+            }
+        }
+    }
+}
+
diff --git a/docs/color.rst b/docs/color.rst
index 9bcc827..10cddbb 100644
--- a/docs/color.rst
+++ b/docs/color.rst
@@ -57,7 +57,7 @@ to bands using the ``write_colormap()`` method.
             meta = src.meta
 
         with rasterio.open('/tmp/colormap.tif', 'w', **meta) as dst:
-            dst.write_band(1, shade)
+            dst.write(shade, indexes=1)
             dst.write_colormap(
                 1, {
                     0: (255, 0, 0, 255), 
diff --git a/docs/concurrency.rst b/docs/concurrency.rst
index 464b110..0b6cde7 100644
--- a/docs/concurrency.rst
+++ b/docs/concurrency.rst
@@ -104,11 +104,7 @@ Here is the program in examples/concurrent-cpu-bound.py.
 
                             result, window = future_to_window[future]
 
-                            # Since there's no multiband write() method yet in
-                            # Rasterio, we use write_band for each part of the
-                            # 3D data array.
-                            for i, arr in enumerate(result, 1):
-                                dst.write_band(i, arr, window=window)
+                            dst.write(arr, window=window)
 
 
     if __name__ == '__main__':
diff --git a/docs/configuration.rst b/docs/configuration.rst
new file mode 100644
index 0000000..1309ed4
--- /dev/null
+++ b/docs/configuration.rst
@@ -0,0 +1,88 @@
+GDAL Option Configuration
+=========================
+
+.. todo::
+
+    Why to use Env() instead of drivers().
+
+    When to use with rasterio.Env() instead of a bare rasterio.open()
+
+
+GDAL format drivers and some parts of the library are configurable.
+
+From https://trac.osgeo.org/gdal/wiki/ConfigOptions:
+
+    ConfigOptions are normally used to alter the default behavior of GDAL
+    and OGR drivers and in some cases the GDAL and OGR core. They are
+    essentially global variables the user can set.
+
+GDAL Example
+------------
+
+The following is from `GDAL's test suite <https://github.com/OSGeo/gdal/blob/0b75aa3c39e6d126439fb17eed939de39f6f3720/autotest/gcore/tiff_read.py#L117-L119>`__.
+
+.. code-block:: python
+
+    gdal.SetConfigOption('GTIFF_FORCE_RGBA', 'YES')
+    ds = gdal.Open('data/stefan_full_greyalpha.tif')
+    gdal.SetConfigOption('GTIFF_FORCE_RGBA', None)
+
+With GDAL's C or Python API, you call a function once to set a global
+configuration option before you need it and once again after you're through
+to unset it.
+
+Downsides of this style of configuration include:
+
+- Options can be configured far from the code they affect.
+- There is no API for finding what options are currently set.
+- If ``gdal.Open()`` raises an exception in the code above, the
+  ``GTIFF_FORCE_RGBA`` option will not be unset.
+
+That code example can be generalized to multiple options and made to
+recover better from errors.
+
+.. code-block:: python
+
+    options = {'GTIFF_FORCE_RGBA': 'YES'}
+    for key, val in options.items():
+        gdal.SetConfigOption(key, val)
+    try:
+        ds = gdal.Open('data/stefan_full_greyalpha.tif')
+    finally:
+        for key, val in options.items():
+            gdal.SetConfigOption(key, None)
+
+This is better, but has a lot of boilerplate. Rasterio uses elements of Python
+syntax, keyword arguments and the |WITHST|_ statement, to make this cleaner
+and easier to use.
+
+Rasterio
+--------
+
+.. code-block:: python
+
+    with rasterio.Env(GTIFF_FORCE_RGBA=True, CPL_DEBUG=True):
+        with rasterio.open('data/stefan_full_greyalpha.tif') as ds:
+           # Suite of code accessing dataset ``ds`` follows...
+
+The object returned when you call ``rasterio.Env()`` is a context manager.  It
+handles the GDAL configuration for a specific block of code and resets the
+configuration when the block exits for any reason, success or failure. The
+Rasterio ``with Env()`` pattern organizes GDAL configuration into single
+statements and makes its relationship to a block of code clear.
+
+If you want to know what options are configured at any time, you could bind it
+to a name like so.
+
+.. code-block:: python
+
+    with rasterio.Env(GTIFF_FORCE_RGBA=True, CPL_DEBUG=True) as env:
+        for key, val in env.options.items():
+            print(key, val)
+
+    # Prints:
+    # ('GTIFF_FORCE_RGBA', True)
+    # ('CPL_DEBUG', True)
+
+.. |WITHST| replace:: ``with``
+.. _WITHST: https://docs.python.org/2/reference/compound_stmts.html#withhttps://docs.python.org/2/reference/compound_stmts
diff --git a/docs/cookbook.rst b/docs/cookbook.rst
index 4868f8e..66a9f5f 100644
--- a/docs/cookbook.rst
+++ b/docs/cookbook.rst
@@ -16,13 +16,13 @@ the fundamentals.
 Generating summary statistics for each band
 -------------------------------------------
 
-.. literalinclude:: recipies/band_summary_stats.py
+.. literalinclude:: recipes/band_summary_stats.py
     :language: python
     :linenos:
 
 .. code::
 
-    $ python docs/recipies/band_summary_stats.py
+    $ python docs/recipes/band_summary_stats.py
     [{'max': 255, 'mean': 29.94772668847656, 'median': 13.0, 'min': 0},
      {'max': 255, 'mean': 44.516147889382289, 'median': 30.0, 'min': 0},
      {'max': 255, 'mean': 48.113056354742945, 'median': 30.0, 'min': 0}]
@@ -39,13 +39,13 @@ Reproject/warp a raster to a different CRS
 Reproject to a Transverse Mercator projection, Hawaii zone 3 (ftUS),
 aka EPSG code 3759. 
 
-.. literalinclude:: recipies/reproject.py
+.. literalinclude:: recipes/reproject.py
     :language: python
     :linenos:
 
 .. code::
 
-    $ python docs/recipies/reproject.py
+    $ python docs/recipes/reproject.py
 
 
 The original image
@@ -81,13 +81,13 @@ is also set to be the extent of the features in the shapefile.
 We can then use the updated spatial transform and raster height and width
 to write the masked raster to a new file.
 
-.. literalinclude:: recipies/mask_shp.py
+.. literalinclude:: recipes/mask_shp.py
     :language: python
     :linenos:
 
 .. code::
 
-    $ python docs/recipies/mask_shp.py
+    $ python docs/recipes/mask_shp.py
 
 
 The original image with the shapefile overlayed
@@ -115,17 +115,17 @@ Creating a least cost path
 Using a scipy filter to smooth a raster
 ---------------------------------------
 
-This recipie demonstrates the use of scipy's `signal processing filters <http://docs.scipy.org/doc/scipy/reference/signal.html#signal-processing-scipy-signal>`_ to manipulate multi-band raster imagery
+This recipe demonstrates the use of scipy's `signal processing filters <http://docs.scipy.org/doc/scipy/reference/signal.html#signal-processing-scipy-signal>`_ to manipulate multi-band raster imagery
 and save the results to a new GeoTIFF. Here we apply a median filter to smooth
 the image and remove small inclusions (at the expense of some sharpness and detail).
 
-.. literalinclude:: recipies/filter.py
+.. literalinclude:: recipes/filter.py
     :language: python
     :linenos:
 
 .. code::
 
-    $ python docs/recipies/filter.py
+    $ python docs/recipes/filter.py
 
 
 The original image
@@ -141,15 +141,15 @@ With median filter applied
 Using skimage to adjust the saturation of a RGB raster
 ------------------------------------------------------
 
-This recipie demonstrates the use of manipulating color with the scikit image `color module <http://scikit-image.org/docs/stable/api/skimage.color.html>`_.
+This recipe demonstrates the use of manipulating color with the scikit image `color module <http://scikit-image.org/docs/stable/api/skimage.color.html>`_.
 
-.. literalinclude:: recipies/saturation.py
+.. literalinclude:: recipes/saturation.py
     :language: python
     :linenos:
 
 .. code::
 
-    $ python docs/recipies/saturation.py
+    $ python docs/recipes/saturation.py
 
 
 The original image
@@ -161,3 +161,18 @@ With increased saturation
 
 .. image:: img/saturation.jpg
     :scale: 50 %
+
+
+Generating a KMZ from a raster
+------------------------------
+
+A raster can be converted to a KMZ and opened in Google Earth using ``rasterio`` to access the raster metadata. Executing
+
+.. code::
+
+    $ python docs/recipes/raster_to_kmz.py
+
+creates the file ``green_box.tif``, which is a green image that extends from longitude -36 to -35 and latitude 74 to 75 in ``EPSG:4326`` projection, and then embeds this raster in a KMZ file ``green_box.kmz``. In Google Earth, we can see the box inside Greenland (screenshot below).
+
+.. image:: img/green_box_kmz.png
+    :scale: 50 %
diff --git a/docs/data_model.rst b/docs/data_model.rst
index a124e05..fa2b0c8 100644
--- a/docs/data_model.rst
+++ b/docs/data_model.rst
@@ -1,11 +1,10 @@
 Data Model
 ==========
 
-.. todo:: 
+.. todo::
 
     Datasets, Bands, Ndarrays
 
     Design Decisions
-       
-    Relationship to GDAL
 
+    Relationship to GDAL
diff --git a/docs/features.rst b/docs/features.rst
index da05e29..7f0c342 100644
--- a/docs/features.rst
+++ b/docs/features.rst
@@ -91,7 +91,7 @@ written to disk like this,
             count=1, 
             width=src.width, 
             height=src.height) as dst:
-        dst.write_band(1, image)
+        dst.write(image, indexes=1)
 
 has a black background and white foreground features.
 
diff --git a/docs/image_options.rst b/docs/image_options.rst
index e1aa0bb..105a847 100644
--- a/docs/image_options.rst
+++ b/docs/image_options.rst
@@ -1,12 +1,21 @@
-Image Options
-*************
+Options
+*******
 
-Driver Options
+GDAL's format drivers have many `configuration options`_.
+These options come in two flavors:
+
+    * General GDAL options
+    * Driver-specific creation options 
+
+General Options
 -----------------
-GDAL's format drivers have many [configuration
-options](https://trac.osgeo.org/gdal/wiki/ConfigOptions) The way to set options
-for rasterio is via ``rasterio.drivers()``. Options set on entering are deleted
-on exit.
+
+GDAL options are typically set as environment variables. While
+environment variables will influence the behavior of ``rasterio``, we
+highly recommended avoiding them in favor of defining behavior programatically.
+
+The preferred way to set options for rasterio is via ``rasterio.drivers()``.
+Options set on entering the context are deleted on exit.
 
 .. code-block:: python
 
@@ -22,16 +31,36 @@ on exit.
 Use native Python forms (``True`` and ``False``) for boolean options. Rasterio
 will convert them GDAL's internal forms.
 
+See the `configuration options`_ page for a complete list of available options.
+
 
 Creation options
 -----------------
 
-Blocksize
-tiled
+Each format has it's own set of driver-specific creation options that can be used to
+fine tune the output rasters. For details on a particular driver, see the `formats list`_.
+
+For the purposes of this document, we will focus on the `GeoTIFF creation options`_.
+Some of the common GeoTIFF creation options include:
+
+  * ``TILED``, ``BLOCKXSIZE``, and ``BLOCKYSIZE`` to define the internal tiling
+  * ``COMPRESS`` to define the compression method
+  * ``PHOTOMETRIC`` to define the band's color interpretation
+
+To specify these creation options in python code, you pass them as keyword arguments
+to the ``rasterio.open()`` command in write mode::
+
+    with rasterio.open("output.tif", 'w', **src.meta, compress="JPEG",
+                       tiled=True, blockxsize=256, blockysize=256,
+                       photometric="YCBCR") as dest:
+        ...
+
+Or at the command line, ``rio`` commands will accept multiple ``--co`` options::
 
-Compression
------------
+    rio copy source.tif dest.tif --co tiled=true
 
-Interleaving
-------------
+                       
 
+.. _configuration options: https://trac.osgeo.org/gdal/wiki/ConfigOptions
+.. _formats list: http://gdal.org/formats_list.html
+.. _GeoTIFF creation options: http://gdal.org/frmt_gtiff.html
diff --git a/docs/img/green_box_kmz.png b/docs/img/green_box_kmz.png
new file mode 100644
index 0000000..967d95e
Binary files /dev/null and b/docs/img/green_box_kmz.png differ
diff --git a/docs/index.rst b/docs/index.rst
index 183ed81..d8b7f6a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -26,6 +26,8 @@ And an example of use in python
 .. code:: python
 
     import rasterio
+
+
     with rasterio.open('data.tif') as src:
         array = src.read()
 
diff --git a/docs/installation.rst b/docs/installation.rst
index 5a607c9..d27508e 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -48,6 +48,14 @@ this from the downloads folder:
     $ pip install GDAL-1.11.2-cp27-none-win32.whl
     $ pip install rasterio-0.24.0-cp27-none-win32.whl
 
+
+Installing with Anaconda
+*************************
+To install rasterio on the Anaconda Python distribution, please visit the
+`rasterio conda-forge`_ page for install instructions. This build is maintained
+separately from the rasterio distribution on PyPi and packaging issues should
+be addressed on the `rasterio conda-forge`_ issue tracker. 
+
 Installing from the source distribution
 ***************************************
 
@@ -155,3 +163,5 @@ GitHub as done in `rio-plugin-example
     script: 
       - py.test
 
+
+.. _rasterio conda-forge: https://github.com/conda-forge/rasterio-feedstock
diff --git a/docs/osgeo_gdal_migration.rst b/docs/osgeo_gdal_migration.rst
index 9a983db..41abf69 100644
--- a/docs/osgeo_gdal_migration.rst
+++ b/docs/osgeo_gdal_migration.rst
@@ -36,9 +36,10 @@ The ``rasterio`` code:
 * follows the conventions of python file handles
 * uses context managers to safely manage memory, environment variables and file resources
 * will raise proper exceptions (i.e. ``IOError`` if the file does not exist)
+* uses a driver context to avoid reliance on environment variables to define behavior
+* readability is subjective but most Python programmers would agree that the ``rasterio`` example is easier to read and understand.
 
-Of course, readability is subjective but I think most Python programmers would agree that the
-``rasterio`` example is easier to understand and debug as well.
+For a more detailed look at some unexpected behaviors of the ``osgeo.gdal`` module, see the `Python Gotchas`_ page on the GDAL wiki. 
 
 .. todo::
 
@@ -59,3 +60,4 @@ Migrating
     Practical tips and examples of porting common use cases in both python and cli.
     Some overlap with the cookbook here, so probably best to reference it when appropriate.
 
+.. _Python Gotchas: https://trac.osgeo.org/gdal/wiki/PythonGotchas
diff --git a/docs/rasterio.rst b/docs/rasterio.rst
index 5bd6c5d..c103653 100644
--- a/docs/rasterio.rst
+++ b/docs/rasterio.rst
@@ -30,3 +30,15 @@ Submodules
    rasterio.warp
    rasterio.windows
 
+
+Source Interface
+-----------------
+
+Instances of these classes are returned from
+``rasterio.open``
+
+.. autoclass:: rasterio._io.RasterReader
+    :inherited-members:
+
+.. autoclass:: rasterio._io.RasterUpdater
+    :inherited-members:
diff --git a/docs/recipies/band_summary_stats.py b/docs/recipes/band_summary_stats.py
similarity index 100%
rename from docs/recipies/band_summary_stats.py
rename to docs/recipes/band_summary_stats.py
diff --git a/docs/recipies/filter.py b/docs/recipes/filter.py
similarity index 100%
rename from docs/recipies/filter.py
rename to docs/recipes/filter.py
diff --git a/docs/recipies/mask_shp.py b/docs/recipes/mask_shp.py
similarity index 100%
rename from docs/recipies/mask_shp.py
rename to docs/recipes/mask_shp.py
diff --git a/docs/recipes/raster_to_kmz.py b/docs/recipes/raster_to_kmz.py
new file mode 100644
index 0000000..9f901a7
--- /dev/null
+++ b/docs/recipes/raster_to_kmz.py
@@ -0,0 +1,57 @@
+import os
+import zipfile
+
+from affine import Affine
+import numpy as np
+import rasterio
+
+width = 100
+height = 100
+count = 3
+box_affine = Affine(0.01, 0, -36, 0, -0.01, 75)
+crs = {'init': 'epsg:4326'} 
+dtype = "uint8"
+
+raster_filename = "/tmp/green_box.tif"
+kml_filename = "/tmp/green_box.kml"
+kmz_filename = "/tmp/green_box.kmz"
+kml_im_path = os.path.join("files", os.path.basename(raster_filename))
+
+with rasterio.open(raster_filename, "w", driver="GTiff", width=width,
+                   height=height, count=count, crs=crs, transform=box_affine,
+                   dtype=dtype) as dest:
+    # initialize a 3 channel uint8 array for our RGB image, and set
+    # the green channel to 160, which will produce a solid green image
+    data = np.zeros([count, height, width], dtype=dtype)
+    data[1, :, :] = 160
+    dest.write(data)
+
+with rasterio.open(raster_filename, "r") as src:
+    kml_params = {"north": src.bounds.top, "south": src.bounds.bottom,
+                  "east": src.bounds.right, "west": src.bounds.left,
+                  "kml_im_path": kml_im_path}
+
+# define basic kml string to specify how image is display in google earth
+kml_string = """<?xml version="1.0" encoding="utf-8"?>
+<kml xmlns="http://earth.google.com/kml/2.1">
+  <Folder>
+    <name>KMZ from raster</name>
+    <GroundOverlay>
+       <name>Greenland green box</name>
+       <Icon>
+          <href>{kml_im_path}</href>
+       </Icon>
+       <LatLonBox>
+          <north>{north}</north>
+          <south>{south}</south>
+          <east>{east}</east>
+          <west>{west}</west>
+       </LatLonBox>
+    </GroundOverlay>
+  </Folder>
+</kml>""".format(**kml_params) 
+
+# write kml string and image to the output kmz file
+with zipfile.ZipFile(kmz_filename, "w") as kmz:
+    kmz.writestr(kml_filename, kml_string)
+    kmz.write(raster_filename, kml_im_path)
diff --git a/docs/recipies/reproject.py b/docs/recipes/reproject.py
similarity index 100%
rename from docs/recipies/reproject.py
rename to docs/recipes/reproject.py
diff --git a/docs/recipies/saturation.py b/docs/recipes/saturation.py
similarity index 100%
rename from docs/recipies/saturation.py
rename to docs/recipes/saturation.py
diff --git a/docs/reproject.rst b/docs/reproject.rst
index 05507e3..bd0a096 100644
--- a/docs/reproject.rst
+++ b/docs/reproject.rst
@@ -157,7 +157,7 @@ the output dataset's transform matrix and, thereby, its spatial extent.
                     dst_crs=src.crs,
                     resampling=RESAMPLING.nearest)
 
-                dst.write_band(i, dest)
+                dst.write(dest, indexes=i)
 
 .. image:: https://farm8.staticflickr.com/7399/16390100651_54f01b8601_b_d.jpg)
 
diff --git a/docs/resampling.rst b/docs/resampling.rst
index e86f6d9..5a3dc31 100644
--- a/docs/resampling.rst
+++ b/docs/resampling.rst
@@ -1,7 +1,109 @@
+Resampling
+**********
+
+ For details on changing coordinate reference systems, see `Reprojection`.
+
+
+Up and Downsampling
+-------------------
+*Resampling* refers to changing the cell values due to changes in the raster cell grid. This can occur during reprojection. Even if the crs is not changing, we may want to change the effective cell size of an existing dataset.
+
+*Upsampling* refers to cases where we are converting to higher resolution/smaller cells.
+*Downsampling* is resampling to lower resolution/larger cellsizes.
+
+There are three potential ways to perform up/downsampling.
+
+Use reproject
+~~~~~~~~~~~~~~~~~
+If you use ``reproject`` but keep the same CRS, you can utilize the underlying GDAL algorithms
+to resample your data.
+
+This involves coordinating the size of your output array with the
+cell size in it's associated affine transform. In other words, if you *multiply* the resolution
+by ``x``, you need to *divide* the affine parameters defining the cell size by ``x``
+
+.. code-block:: python
+
+    arr = src.read()
+    newarr = np.empty(shape=(arr.shape[0],  # same number of bands
+                             round(arr.shape[1] * 1.5), # 150% resolution
+                             round(arr.shape[2] * 1.5)))
+
+    # adjust the new affine transform to the 150% smaller cell size
+    aff = src.affine
+    newaff = Affine(aff.a / 1.5, aff.b, aff.c,
+                    aff.d, aff.e / 1.5, aff.f)
+
+    reproject(
+        arr, newarr,
+        src_transform = aff,
+        dst_transform = newaff,
+        src_crs = src.crs,
+        dst_crs = src.crs,
+        resample = Resampling.bilinear)
+
+
+Use scipy
+~~~~~~~~~~~~~
+
+You can also use `scipy.ndimage.interpolation.zoom`_ to "zoom" with a configurable spline interpolation
+that differs from the resampling methods available in GDAL. This may not be appropriate for all data so check the results carefully. You must adjust the affine transform just as we did above.
+
+.. code-block:: python
+
+    from scipy.ndimage.interpolation import zoom
+
+    # Increase resolution, decrease cell size by 150%
+    # Note we only zoom on axis 1 and 2
+    # axis 0 (our band axis) stays fixed
+    arr = src.read()
+    newarr = zoom(arr, zoom=[1, 1.5, 1.5], order=3, prefilter=False)
+
+    # Adjust original affine transform
+    aff = src.affine
+    newaff = Affine(aff.a / 1.5, aff.b, aff.c,
+                    aff.d, aff.e / 1.5, aff.f)
+
+
+Use decimated reads
+~~~~~~~~~~~~~~~~~~~
+
+Another technique for quickly up/downsampling data is to use decimated reads.
+By reading from a raster source into an ``out`` array of a specified size, you
+are effectively resampling the data to a new size.
+
+.. warning::
+
+     The underlying GDALRasterIO function does not support different resampling
+     methods. You are stuck with the default which can result in unwanted effects
+     and data loss in some cases. We recommend using a different method unless
+     you are upsampling by an integer factor.
+
+As per the previous two examples, you must also adjust the affine accordingly.
+
+Note that this method is only recommended for *increasing* resolution by an integer factor.
+
+.. code-block:: python
+
+    newarr = np.empty(shape=(arr.shape[0],  # same number of bands
+                             round(arr.shape[1] * 2), # double resolution
+                             round(arr.shape[2] * 2)))
+
+    arr.read(out=newarr)  # newarr is changed in-place
+
+
 Resampling Methods
-******************
+------------------
+
+When you change the raster cell grid, you must recalulate the pixel values. There is no "correct" way to do this as all methods involve some interpolation.
+
+The current resampling methods can be found in the `rasterio.enums`_ source.
+
+Of note, the default ``Resampling.nearest`` method may not be suitable for continuous data. In those
+cases, ``Resampling.bilinear`` and ``Resampling.cubic`` are better suited.
+Some specialized statistical resampling method exist, e.g. ``Resampling.average``, which may be
+useful when certain numerical properties of the data are to be retained.
 
-.. todo::
 
-    explain why you would use nearest vs bilinear/cubic/etc vs summary stats.
-    categorical, continuous data, imagery all have different concerns.
+.. _scipy.ndimage.interpolation.zoom: http://docs.scipy.org/doc/scipy-0.16.1/reference/generated/scipy.ndimage.interpolation.zoom.html
+.. _rasterio.enums: https://github.com/mapbox/rasterio/blob/master/rasterio/enums.py#L28
diff --git a/docs/windowed-rw.rst b/docs/windowed-rw.rst
index 8ff6290..436180c 100644
--- a/docs/windowed-rw.rst
+++ b/docs/windowed-rw.rst
@@ -87,7 +87,7 @@ and 50 pixels to the right of the upper left corner of the GeoTIFF.
             '/tmp/example.tif', 'w',
             driver='GTiff', width=500, height=300, count=1,
             dtype=image.dtype) as dst:
-        dst.write_band(1, image, window=((30, 180), (50, 300)))
+        dst.write(image, window=((30, 180), (50, 300)), indexes=1)
     
 The result:
 
@@ -113,7 +113,7 @@ Below, the window is scaled to one third of the source image.
             driver='GTiff', width=500, height=300, count=3,
             dtype=r.dtype) as dst:
         for k, arr in [(1, b), (2, g), (3, r)]:
-            dst.write_band(k, arr, window=write_window)
+            dst.write(arr, indexes=k, window=write_window)
 
 And the result:
 
@@ -153,7 +153,7 @@ destination dataset.
             driver='GTiff', width=500, height=300, count=3,
             dtype=r.dtype) as dst:
         for k, arr in [(1, b), (2, g), (3, r)]:
-            dst.write_band(k, arr, window=write_window)
+            dst.write(arr, window=write_window, indexes=k)
 
 This example also demonstrates decimation.
 
@@ -197,17 +197,14 @@ with the same full extent.
 
 .. code-block:: python
 
-    from rasterio import windows
-
-    # Full window is ((0, 1000), (0, 500))
-    window1 = ((100, 500), (10, 500))
-    window2 = ((10, 150), (50, 250))
-
-    outer = windows.union([window1, window2])
-    # outer = ((10, 500), (10, 500))
-
-    inner = windows.intersection([window1, window2])
-    # inner = ((100, 150), (50, 250))
+    >>> from rasterio import windows
+    >>> # Full window is ((0, 1000), (0, 500))
+    >>> window1 = ((100, 500), (10, 500))
+    >>> window2 = ((10, 150), (50, 250))
+    >>> windows.union(window1, window2)
+    ((10, 500), (10, 500))
+    >>> windows.intersection(window1, window2)
+    ((100, 150), (50, 250))
 
 
 Blocks
@@ -242,7 +239,7 @@ The block windows themselves can be had from the block_windows function.
     ...
 
 This function returns an iterator that yields a pair of values. The second is
-a window tuple that can be used in calls to `read` or `write_band`. The first
+a window tuple that can be used in calls to `read` or `write`. The first
 is the pair of row and column indexes of this block within all blocks of the
 dataset.
 
diff --git a/docs/working_with_datasets.rst b/docs/working_with_datasets.rst
index 7dbc181..7f2ddb5 100644
--- a/docs/working_with_datasets.rst
+++ b/docs/working_with_datasets.rst
@@ -4,7 +4,6 @@ Working with Datasets
 .. todo::
 
     * working with ndarrays
-    * src.profile
 
 Attributes
 ----------
@@ -55,3 +54,30 @@ row)``.
     >>> src.affine * (col, row)
     (339315.0, 2611485.0)
 
+
+Profile
+-------
+The ``src.profile`` property is the union of meta, creation options saved as tags, and tiling options.
+The primary use of profile is to provide a canonical way of creating a dataset clone, 
+encapsulating all the necessary metadata required to recreate a dataset::
+
+    with rasterio.open('example.tif') as src:
+        with rasterio.open('clone.tif', 'w', **src.profile) as dst:
+            dst.write(src.read()) 
+
+A common pattern for using the profile is to copy a source profile, update it slightly 
+to reflect any changes, and use the updated copy to create the output::
+
+    # we start with the profile of the source file
+    profile = src.profile.copy()
+
+    # but then change the band count to 1, set the
+    # dtype to uint8, and specify LZW compression.
+    profile.update(
+        dtype=rasterio.uint8,
+        count=1,
+        compress='lzw')
+
+    # And use the updated profile as kwargs for our destination file
+    with open('destination.tif', 'w', **profile) as dst:
+        ...
diff --git a/docs/writing.rst b/docs/writing.rst
index 4099c91..d0fc849 100644
--- a/docs/writing.rst
+++ b/docs/writing.rst
@@ -7,7 +7,6 @@ Writing Datasets
     * appending to existing data
     * context manager
     * write 3d vs write 2d
-    * profile.update
     * document issues with writing compressed files (per #77)
     * discuss and refer to topics
         * creation options
diff --git a/examples/async-rasterio.py b/examples/async-rasterio.py
index 4e50b84..2aeb6ff 100644
--- a/examples/async-rasterio.py
+++ b/examples/async-rasterio.py
@@ -64,9 +64,7 @@ def main(infile, outfile, with_threads=False):
                     else:
                         compute(data, result)
                     
-                    # Write the result.
-                    for i, arr in enumerate(result, 1):
-                        dst.write_band(i, arr, window=window)
+                    dst.write(result, window=window)
 
                 # Queue up the loop's tasks.
                 tasks = [asyncio.Task(process_window(window)) 
diff --git a/examples/concurrent-cpu-bound.py b/examples/concurrent-cpu-bound.py
index 98de30c..0053a05 100644
--- a/examples/concurrent-cpu-bound.py
+++ b/examples/concurrent-cpu-bound.py
@@ -15,6 +15,7 @@ import numpy
 import rasterio
 from rasterio._example import compute
 
+
 def main(infile, outfile, num_workers=4):
 
     with rasterio.drivers():
@@ -60,11 +61,7 @@ def main(infile, outfile, num_workers=4):
 
                         result, window = future_to_window[future]
 
-                        # Since there's no multiband write() method yet in
-                        # Rasterio, we use write_band for each part of the
-                        # 3D data array.
-                        for i, arr in enumerate(result, 1):
-                            dst.write_band(i, arr, window=window)
+                        dst.write(result, window=window)
 
 
 if __name__ == '__main__':
diff --git a/examples/decimate.py b/examples/decimate.py
index cd0c440..b15ed8a 100644
--- a/examples/decimate.py
+++ b/examples/decimate.py
@@ -21,11 +21,10 @@ with rasterio.drivers():
             **meta
             ) as dst:
         for k, a in [(1, b), (2, g), (3, r)]:
-            dst.write_band(k, a)
+            dst.write(a, indexes=k)
 
     outfilename = os.path.join(tempfile.mkdtemp(), 'decimate.jpg')
 
     rasterio.copy(tmpfilename, outfilename, driver='JPEG', quality='30')
 
 info = subprocess.call(['open', outfilename])
-
diff --git a/examples/rasterize_geometry.py b/examples/rasterize_geometry.py
index bfbc43e..804ecd6 100644
--- a/examples/rasterize_geometry.py
+++ b/examples/rasterize_geometry.py
@@ -5,12 +5,18 @@ import rasterio
 from rasterio.features import rasterize
 from rasterio.transform import IDENTITY
 
+
 logging.basicConfig(stream=sys.stderr, level=logging.INFO)
 logger = logging.getLogger('rasterize_geometry')
 
 
 rows = cols = 10
-geometry = {'type':'Polygon','coordinates':[[(2,2),(2,4.25),(4.25,4.25),(4.25,2),(2,2)]]}
+
+geometry = {
+    'type': 'Polygon',
+    'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]}
+
+
 with rasterio.drivers():
     result = rasterize([geometry], out_shape=(rows, cols))
     with rasterio.open(
@@ -24,4 +30,4 @@ with rasterio.drivers():
             nodata=0,
             transform=IDENTITY,
             crs={'init': "EPSG:4326"}) as out:
-        out.write_band(1, result.astype(numpy.uint8))
+        out.write(result.astype(numpy.uint8), indexes=1)
diff --git a/examples/reproject.py b/examples/reproject.py
index 89e6985..cadf4a5 100644
--- a/examples/reproject.py
+++ b/examples/reproject.py
@@ -54,6 +54,6 @@ with rasterio.drivers():
             nodata=0,
             transform=dst_transform,
             crs=dst_crs) as dst:
-        dst.write_band(1, destination)
+        dst.write(destination, indexes=1)
 
 info = subprocess.call(['open', tiffname])
diff --git a/examples/sieve.py b/examples/sieve.py
index d074745..9b0b0d7 100644
--- a/examples/sieve.py
+++ b/examples/sieve.py
@@ -29,7 +29,7 @@ with rasterio.drivers():
     kwargs = src.meta
     kwargs['transform'] = kwargs.pop('affine')
     with rasterio.open('example-sieved.tif', 'w', **kwargs) as dst:
-        dst.write_band(1, sieved)
+        dst.write(sieved, indexes=1)
 
 # Dump out gdalinfo's report card and open (or "eog") the TIFF.
 print(subprocess.check_output(
diff --git a/examples/total.py b/examples/total.py
index d95a0fa..8bb9900 100644
--- a/examples/total.py
+++ b/examples/total.py
@@ -28,7 +28,7 @@ with rasterio.drivers(CPL_DEBUG=True):
         compress='lzw')
 
     with rasterio.open('example-total.tif', 'w', **kwargs) as dst:
-        dst.write_band(1, total.astype(rasterio.uint8))
+        dst.write(total.astype(rasterio.uint8), indexes=1)
 
 # Dump out gdalinfo's report card and open the image.
 info = subprocess.check_output(
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
index 419e53f..b0c758e 100644
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -1,18 +1,27 @@
-# rasterio
+"""Rasterio"""
+
 from __future__ import absolute_import
 
 from collections import namedtuple
 import logging
+try:
+    from logging import NullHandler
+except ImportError:  # pragma: no cover
+    class NullHandler(logging.Handler):
+        def emit(self, record):
+            pass
+import warnings
 
 from rasterio._base import (
     eval_window, window_shape, window_index, gdal_version)
-from rasterio._drivers import driver_count, GDALEnv
 from rasterio.dtypes import (
     bool_, ubyte, uint8, uint16, int16, uint32, int32, float32, float64,
-    complex_)
+    complex_, check_dtype)
+from rasterio.env import defenv, Env
 from rasterio.five import string_types
 from rasterio.profiles import default_gtiff_profile
 from rasterio.transform import Affine, guard_transform
+from rasterio.vfs import parse_path
 from rasterio import windows
 
 # These modules are imported from the Cython extensions, but are also import
@@ -22,45 +31,43 @@ from rasterio import _err, coords, enums, vfs
 # Classes in rasterio._io are imported below just before we need them.
 
 __all__ = [
-    'band', 'open', 'drivers', 'copy', 'pad']
-__version__ = "0.34.0"
+    'band', 'open', 'copy', 'pad']
+__version__ = "0.35.0"
 __gdal_version__ = gdal_version()
 
-log = logging.getLogger('rasterio')
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
+# Rasterio attaches NullHandler to the 'rasterio' logger and its
+# descendents. See
+# https://docs.python.org/2/howto/logging.html#configuring-logging-for-a-library
+# Applications must attach their own handlers in order to see messages.
+# See rasterio/rio/main.py for an example.
+log = logging.getLogger(__name__)
 log.addHandler(NullHandler())
 
 
-def open(
-        path, mode='r',
-        driver=None,
-        width=None, height=None,
-        count=None,
-        crs=None, transform=None,
-        dtype=None,
-        nodata=None,
-        **kwargs):
-    """Open file at ``path`` in ``mode`` "r" (read), "r+" (read/write),
-    or "w" (write) and return a ``Reader`` or ``Updater`` object.
+def open(path, mode='r', driver=None, width=None, height=None,
+         count=None, crs=None, transform=None, dtype=None, nodata=None,
+         **kwargs):
+    """Open file at ``path`` in ``mode`` 'r' (read), 'r+' (read and
+    write), or 'w' (write) and return a dataset Reader or Updater
+    object.
 
     In write mode, a driver name such as "GTiff" or "JPEG" (see GDAL
-    docs or ``gdal_translate --help`` on the command line), ``width``
-    (number of pixels per line) and ``height`` (number of lines), the
-    ``count`` number of bands in the new file must be specified.
-    Additionally, the data type for bands such as ``rasterio.ubyte`` for
-    8-bit bands or ``rasterio.uint16`` for 16-bit bands must be
-    specified using the ``dtype`` argument.
+    docs or ``gdal_translate --help`` on the command line),
+    ``width`` (number of pixels per line) and ``height`` (number of
+    lines), the ``count`` number of bands in the new file must be
+    specified.  Additionally, the data type for bands such as
+    ``rasterio.ubyte`` for 8-bit bands or ``rasterio.uint16`` for
+    16-bit bands must be specified using the ``dtype`` argument.
 
     Parameters
     ----------
     mode: string
         "r" (read), "r+" (read/write), or "w" (write)
     driver: string
-        driver code specifying the format name (e.g. "GTiff" or "JPEG")
-        See GDAL docs at http://www.gdal.org/formats_list.html
-        (optional, required for write)
+        driver code specifying the format name (e.g. "GTiff" or
+        "JPEG"). See GDAL docs at
+        http://www.gdal.org/formats_list.html (optional, required
+        for writing).
     width: int
         number of pixels per line
         (optional, required for write)
@@ -78,31 +85,32 @@ def open(
         Coordinate reference system
         (optional, recommended for write)
     transform: Affine instance
-        Affine transformation mapping the pixel space to geographic space
-        (optional, recommended for write)
+        Affine transformation mapping the pixel space to geographic
+        space (optional, recommended for writing).
     nodata: number
         Defines pixel value to be interpreted as null/nodata
         (optional, recommended for write)
 
     Returns
     -------
-    A ``Reader`` or ``Updater`` object.
+    A ``DatasetReader`` or ``DatasetUpdater`` object.
 
     Notes
     -----
-    In write mode, you must specify at least ``width``, ``height``, ``count``
-    and ``dtype``.
+    In write mode, you must specify at least ``width``, ``height``,
+    ``count`` and ``dtype``.
 
-    A coordinate reference system for raster datasets in write mode can
-    be defined by the ``crs`` argument. It takes Proj4 style mappings
-    like
+    A coordinate reference system for raster datasets in write mode
+    can be defined by the ``crs`` argument. It takes Proj4 style
+    mappings like
 
     .. code::
 
-      {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True}
+      {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84',
+       'no_defs': True}
 
-    An affine transformation that maps ``col,row`` pixel coordinates to
-    ``x,y`` coordinates in the coordinate reference system can be
+    An affine transformation that maps ``col,row`` pixel coordinates
+    to ``x,y`` coordinates in the coordinate reference system can be
     specified using the ``transform`` argument. The value should be
     an instance of ``affine.Affine``
 
@@ -119,37 +127,53 @@ def open(
       | y | = | d  e  f | | r |
       | 1 |   | 0  0  1 | | 1 |
 
-        a: rate of change of X with respect to increasing column, i.e.  pixel width
-        b: rotation, 0 if the raster is oriented "north up"
-        c: X coordinate of the top left corner of the top left pixel
-        d: rotation, 0 if the raster is oriented "north up"
-        e: rate of change of Y with respect to increasing row, usually
-                a negative number (i.e. -1 * pixel height) if north-up.
-        f: Y coordinate of the top left corner of the top left pixel
-
-    A 6-element sequence of the affine transformation
-    matrix coefficients in ``c, a, b, f, d, e`` order,
-    (i.e. GDAL geotransform order) will be accepted until 1.0 (deprecated).
-
-    A virtual filesystem can be specified. The ``vfs`` parameter may be
-    an Apache Commons VFS style string beginning with "zip://" or
+      a: rate of change of X with respect to increasing column,
+         i.e. pixel width
+      b: rotation, 0 if the raster is oriented "north up"
+      c: X coordinate of the top left corner of the top left pixel
+      d: rotation, 0 if the raster is oriented "north up"
+      e: rate of change of Y with respect to increasing row,
+         usually a negative number (i.e. -1 * pixel height) if
+         north-up.
+      f: Y coordinate of the top left corner of the top left pixel
+
+    A 6-element sequence of the affine transformation matrix
+    coefficients in ``c, a, b, f, d, e`` order, (i.e. GDAL
+    geotransform order) will be accepted until 1.0 (deprecated).
+
+    A virtual filesystem can be specified. The ``vfs`` parameter may
+    be an Apache Commons VFS style string beginning with "zip://" or
     "tar://"". In this case, the ``path`` must be an absolute path
     within that container.
 
     """
     if not isinstance(path, string_types):
-        raise TypeError("invalid path: %r" % path)
+        raise TypeError("invalid path: {0!r}".format(path))
     if mode and not isinstance(mode, string_types):
-        raise TypeError("invalid mode: %r" % mode)
+        raise TypeError("invalid mode: {0!r}".format(mode))
     if driver and not isinstance(driver, string_types):
-        raise TypeError("invalid driver: %r" % driver)
-
+        raise TypeError("invalid driver: {0!r}".format(driver))
+    if dtype and not check_dtype(dtype):
+        raise TypeError("invalid dtype: {0!r}".format(dtype))
     if transform:
         transform = guard_transform(transform)
     elif 'affine' in kwargs:
         affine = kwargs.pop('affine')
         transform = guard_transform(affine)
 
+    # If there is no currently active GDAL/AWS environment, create one.
+    defenv()
+
+    # Get AWS credentials if we're attempting to access a raster
+    # on S3.
+    pth, archive, scheme = parse_path(path)
+    if scheme == 's3':
+        Env().get_aws_credentials()
+        log.debug("AWS credentials have been obtained")
+
+    # Create dataset instances and pass the given env, which will
+    # be taken over by the dataset's context manager if it is not
+    # None.
     if mode == 'r':
         from rasterio._io import RasterReader
         s = RasterReader(path)
@@ -164,8 +188,7 @@ def open(
         s = writer(path, mode, driver=driver,
                    width=width, height=height, count=count,
                    crs=crs, transform=transform, dtype=dtype,
-                   nodata=nodata,
-                   **kwargs)
+                   nodata=nodata, **kwargs)
     else:
         raise ValueError(
             "mode string must be one of 'r', 'r+', or 'w', not %s" % mode)
@@ -201,13 +224,17 @@ def copy(src, dst, **kw):
     This is the one way to create write-once files like JPEGs.
     """
     from rasterio._copy import RasterCopier
-    with drivers():
-        return RasterCopier()(src, dst, **kw)
+
+    # If there is no currently active GDAL/AWS environment, create one.
+    defenv()
+    return RasterCopier()(src, dst, **kw)
 
 
 def drivers(**kwargs):
-    """Create a gdal environment with registered drivers and
-    creation options.
+    """Create a gdal environment with registered drivers and creation
+    options.
+
+    This function is deprecated; please use ``env.Env`` instead.
 
     Parameters
     ----------
@@ -224,7 +251,8 @@ def drivers(**kwargs):
     -----
     Use as a context manager, ``with rasterio.drivers(): ...``
     """
-    return GDALEnv(**kwargs)
+    warnings.warn("Deprecated; Use env.Env instead", DeprecationWarning)
+    return Env(**kwargs)
 
 
 Band = namedtuple('Band', ['ds', 'bidx', 'dtype', 'shape'])
@@ -284,23 +312,19 @@ def pad(array, transform, pad_width, mode=None, **kwargs):
 
 
 def get_data_window(arr, nodata=None):
-    import warnings
     warnings.warn("Deprecated; Use rasterio.windows instead", DeprecationWarning)
     return windows.get_data_window(arr, nodata)
 
 
 def window_union(data):
-    import warnings
     warnings.warn("Deprecated; Use rasterio.windows instead", DeprecationWarning)
     return windows.union(data)
 
 
 def window_intersection(data):
-    import warnings
     warnings.warn("Deprecated; Use rasterio.windows instead", DeprecationWarning)
     return windows.intersection(data)
 
 def windows_intersect(data):
-    import warnings
     warnings.warn("Deprecated; Use rasterio.windows instead", DeprecationWarning)
     return windows.intersect(data)
diff --git a/rasterio/_base.pxd b/rasterio/_base.pxd
index cd838dc..f8222f6 100644
--- a/rasterio/_base.pxd
+++ b/rasterio/_base.pxd
@@ -20,7 +20,6 @@ cdef class DatasetReader:
     cdef public object _block_shapes
     cdef public object _nodatavals
     cdef public object _read
-    cdef object env
 
     cdef void *band(self, int bidx) except NULL
 
diff --git a/rasterio/_base.pyx b/rasterio/_base.pyx
index 2b9fcda..905c545 100644
--- a/rasterio/_base.pyx
+++ b/rasterio/_base.pyx
@@ -1,6 +1,5 @@
-# The Numpy-free base classes.
-
 # cython: boundscheck=False
+"""Numpy-free base classes."""
 
 from __future__ import absolute_import
 
@@ -13,28 +12,19 @@ import warnings
 from libc.stdlib cimport malloc, free
 
 from rasterio cimport _gdal, _ogr
-from rasterio._drivers import driver_count, GDALEnv
 from rasterio._err import (
     CPLErrors, GDALError, CPLE_IllegalArg, CPLE_OpenFailed, CPLE_NotSupported)
 from rasterio import dtypes
 from rasterio.coords import BoundingBox
-from rasterio.errors import RasterioIOError, CRSError
-from rasterio.transform import Affine
 from rasterio.enums import (
     ColorInterp, Compression, Interleaving, PhotometricInterp)
+from rasterio.env import Env
+from rasterio.errors import RasterioIOError, CRSError
+from rasterio.transform import Affine
 from rasterio.vfs import parse_path, vsi_path
 
 
-log = logging.getLogger('rasterio')
-if 'all' in sys.warnoptions:
-    # show messages in console with: python -W all
-    logging.basicConfig()
-else:
-    # no handler messages shown
-    class NullHandler(logging.Handler):
-        def emit(self, record):
-            pass
-    log.addHandler(NullHandler())
+log = logging.getLogger(__name__)
 
 
 def check_gdal_version(major, minor):
@@ -65,7 +55,6 @@ cdef class DatasetReader(object):
         self._crs = None
         self._crs_wkt = None
         self._read = False
-        self.env = None
     
     def __repr__(self):
         return "<%s RasterReader name='%s' mode='%s'>" % (
@@ -74,21 +63,16 @@ cdef class DatasetReader(object):
             self.mode)
 
     def start(self):
-        self.env = GDALEnv()
-        self.env.start()
-
+        """Start of the dataset reader life cycle."""
         path, archive, scheme = parse_path(self.name)
         path = vsi_path(path, archive=archive, scheme=scheme)
-
         name_b = path.encode('utf-8')
         cdef const char *fname = name_b
-
         try:
             with CPLErrors() as cple:
                 self._hds = _gdal.GDALOpen(fname, 0)
                 cple.check()
         except CPLE_OpenFailed as err:
-            self.env.stop()
             raise RasterioIOError(err.errmsg)
 
         cdef void *drv
@@ -110,6 +94,8 @@ cdef class DatasetReader(object):
         _ = self.meta
 
         self._closed = False
+        log.debug("Dataset %r is started.", self)
+
 
     cdef void *band(self, int bidx) except NULL:
         cdef void *hband = NULL
@@ -118,7 +104,6 @@ cdef class DatasetReader(object):
                 hband = _gdal.GDALGetRasterBand(self._hds, bidx)
                 cple.check()
         except CPLE_IllegalArg as exc:
-            self.env.stop()
             raise IndexError(str(exc))
         return hband
 
@@ -129,7 +114,6 @@ cdef class DatasetReader(object):
                 hband = _gdal.GDALGetRasterBand(self._hds, bidx)
                 cple.check()
         except CPLE_IllegalArg:
-            self.env.stop()
             return False
         return True
 
@@ -250,19 +234,23 @@ cdef class DatasetReader(object):
         if self._hds != NULL:
             _gdal.GDALFlushCache(self._hds)
             _gdal.GDALClose(self._hds)
-        if self.env:
-            self.env.stop()
         self._hds = NULL
+        log.debug("Dataset %r has been stopped.", self)
 
     def close(self):
         self.stop()
         self._closed = True
+        log.debug("Dataset %r has been closed.", self)
+
     
     def __enter__(self):
+        log.debug("Entering Dataset %r context.", self)
         return self
 
     def __exit__(self, type, value, traceback):
         self.close()
+        log.debug("Exited Dataset %r context.", self)
+
 
     def __dealloc__(self):
         if self._hds != NULL:
@@ -282,11 +270,11 @@ cdef class DatasetReader(object):
 
     @property
     def indexes(self):
-        return list(range(1, self.count+1))
+        return tuple(range(1, self.count+1))
 
     @property
     def dtypes(self):
-        """Returns an ordered list of all band data types."""
+        """Returns an ordered tuple of all band data types."""
         cdef void *hband = NULL
         if not self._dtypes:
             if self._hds == NULL:
@@ -295,7 +283,7 @@ cdef class DatasetReader(object):
                 hband = _gdal.GDALGetRasterBand(self._hds, i+1)
                 self._dtypes.append(
                     dtypes.dtype_fwd[_gdal.GDALGetRasterDataType(hband)])
-        return self._dtypes
+        return tuple(self._dtypes)
     
     @property
     def block_shapes(self):
@@ -316,7 +304,7 @@ cdef class DatasetReader(object):
                     raise ValueError("Null band")
                 _gdal.GDALGetBlockSize(hband, &xsize, &ysize)
                 self._block_shapes.append((ysize, xsize))
-        return self._block_shapes
+        return tuple(self._block_shapes)
 
     def get_nodatavals(self):
         cdef void *hband = NULL
@@ -343,11 +331,11 @@ cdef class DatasetReader(object):
                         val < dtypes.dtype_ranges[dtype][0] or
                         val > dtypes.dtype_ranges[dtype][1]):
                     val = None
-                log.debug("Nodata success: %d", success)
-                log.debug("Nodata value: %f", nodataval)
+                log.debug(
+                    "Nodata success: %d, Nodata value: %f", success, nodataval)
                 self._nodatavals.append(val)
 
-        return self._nodatavals
+        return tuple(self._nodatavals)
 
     property nodatavals:
         """Nodata values for each band."""
diff --git a/rasterio/_copy.pyx b/rasterio/_copy.pyx
index fab8593..6d7fda1 100644
--- a/rasterio/_copy.pyx
+++ b/rasterio/_copy.pyx
@@ -1,3 +1,5 @@
+"""Raster copying."""
+
 import logging
 import os
 import os.path
@@ -5,11 +7,7 @@ import os.path
 from rasterio cimport _gdal
 
 
-log = logging.getLogger('rasterio')
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
+log = logging.getLogger(__name__)
 
 
 cdef class RasterCopier:
@@ -52,4 +50,3 @@ cdef class RasterCopier:
 
         if options:
             _gdal.CSLDestroy(options)
-
diff --git a/rasterio/_drivers.pyx b/rasterio/_drivers.pyx
index c28703a..4f0564c 100644
--- a/rasterio/_drivers.pyx
+++ b/rasterio/_drivers.pyx
@@ -1,9 +1,8 @@
-# The GDAL and OGR driver registry.
-# GDAL driver management.
+"""GDAL and OGR driver management."""
 
+import logging
 import os
 import os.path
-import logging
 import sys
 
 from rasterio.five import string_types
@@ -17,7 +16,7 @@ cdef extern from "cpl_conv.h":
 
 
 cdef extern from "cpl_error.h":
-    void CPLSetErrorHandler (void *handler)
+    void CPLSetErrorHandler(void *handler)
 
 
 cdef extern from "gdal.h":
@@ -35,13 +34,6 @@ cdef extern from "ogr_api.h":
     int OGRGetDriverCount()
 
 
-log = logging.getLogger('GDAL')
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
-
-
 level_map = {
     0: 0,
     1: logging.DEBUG,
@@ -50,6 +42,7 @@ level_map = {
     4: logging.CRITICAL }
 
 code_map = {
+    0: 'CPLE_None',
     1: 'CPLE_AppDefined',
     2: 'CPLE_OutOfMemory',
     3: 'CPLE_FileIO',
@@ -70,94 +63,94 @@ code_map = {
     15: 'CPLE_AWSInvalidCredentials',
     16: 'CPLE_AWSSignatureDoesNotMatch'}
 
+log = logging.getLogger(__name__)
+
 
 cdef void * errorHandler(int eErrClass, int err_no, char *msg):
     if err_no in code_map:
-        log.log(level_map[eErrClass], "%s in %s", code_map[err_no], msg)
+        # 'rasterio._gdal' is the name in our logging hierarchy for
+        # messages coming direct from CPLError().
+        logger = logging.getLogger('rasterio._gdal')
+        logger.log(level_map[eErrClass], "%s in %s", code_map[err_no], msg)
 
 
 def driver_count():
     return GDALGetDriverCount() + OGRGetDriverCount()
 
 
+cpdef get_gdal_config(key):
+    cdef const char *key_c = NULL
+    cdef const char *val_c = NULL
+    key_b = key.upper().encode('utf-8')
+    key_c = key_b
+    val_c = CPLGetConfigOption(key_c, NULL)
+    if val_c == NULL:
+        return None
+    else:
+        val_b = val_c
+        val = val_b.decode('utf-8')
+        if val == 'ON':
+            return True
+        elif val == 'OFF':
+            return False
+        else:
+            return val
+
+
+cpdef set_gdal_config(key, val):
+    cdef const char *key_c = NULL
+    cdef const char *val_c = NULL
+    key_b = key.upper().encode('utf-8')
+    key_c = key_b
+    if isinstance(val, string_types):
+        val_b = val.encode('utf-8')
+    else:
+        val_b = ('ON' if val else 'OFF').encode('utf-8')
+    val_c = val_b
+    CPLSetConfigOption(key_c, val_c)
+
+
+cpdef del_gdal_config(key):
+    cdef const char *key_c = NULL
+    key_b = key.upper().encode('utf-8')
+    key_c = key_b
+    CPLSetConfigOption(key_c, NULL)
+
+
 cdef class ConfigEnv(object):
 
     cdef public object options
-    cdef public object prev_options
 
     def __init__(self, **options):
-        self.options = options.copy()
-        self.prev_options = {}
-
-    def enter_config_options(self):
-        """Set GDAL config options."""
-        cdef const char *key_c
-        cdef const char *val_c
-
-        for key, val in self.options.items():
-            key_b = key.upper().encode('utf-8')
-            key_c = key_b
-
-            # Save current value of that key.
-            val_c = CPLGetConfigOption(key_c, NULL)
-            if val_c != NULL:
-                val_b = val_c
-                self.prev_options[key_b] = val_b
-
-            if isinstance(val, string_types):
-                val_b = val.encode('utf-8')
-            else:
-                val_b = ('ON' if val else 'OFF').encode('utf-8')
-            val_c = val_b
-            CPLSetConfigOption(key_c, val_c)
-
-            # Redact AWS credentials.
-            if key in ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY',
-                       'AWS_SESSION_TOKEN', 'AWS_REGION']:
+        self.options = {}
+        self.update_config_options(**self.options)
+
+    def update_config_options(self, **kwargs):
+        """Update GDAL config options."""
+        for key, val in kwargs.items():
+            set_gdal_config(key, val)
+            # Redact AWS credentials for logs
+            if key.upper() in ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY',
+                               'AWS_SESSION_TOKEN']:
                 val = '******'
-            log.debug("Option %s=%s", key, val)
+            log.debug("Set option %s=%s in env %r", key, val, self)
+        self.options.update(**kwargs)
 
-    def exit_config_options(self):
+    def clear_config_options(self):
         """Clear GDAL config options."""
-        cdef const char *key_c
-        cdef const char *val_c
-
         for key in self.options:
-            key_b = key.upper().encode('utf-8')
-            key_c = key_b
-            if key_b in self.prev_options:
-                val_b = self.prev_options[key_b]
-                key_c = key_b; val_c = val_b
-                CPLSetConfigOption(key_c, val_c)
-            else:
-                CPLSetConfigOption(key_c, NULL)
-
-    def __enter__(self):
-        self.enter_config_options()
-        return self
-
-    def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
-        self.exit_config_options()
+            del_gdal_config(key)
+            log.debug("Unset option %s in env %r", key, self)
+        self.options = {}
 
 
 cdef class GDALEnv(ConfigEnv):
 
     def __init__(self, **options):
-        self.options = options.copy()
-        self.prev_options = {}
-
-    def __enter__(self):
+        super(GDALEnv, self).__init__(**options)
         self.start()
-        self.enter_config_options()
-        return self
-
-    def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
-        self.exit_config_options()
-        self.stop()
 
     def start(self):
-        cdef const char *key_c
-        cdef const char *val_c
         GDALAllRegister()
         OGRRegisterAll()
         CPLSetErrorHandler(<void *>errorHandler)
@@ -176,9 +169,13 @@ cdef class GDALEnv(ConfigEnv):
             whl_datadir = os.path.abspath(
                 os.path.join(os.path.dirname(__file__), "proj_data"))
             os.environ['PROJ_LIB'] = whl_datadir
+        log.debug("Env %r has been started", self)
 
     def stop(self):
-        CPLSetErrorHandler(NULL)
+        # NB: do not restore the CPL error handler to its default
+        # state here. If you do, log messages will be written to stderr
+        # by GDAL instead of being sent to Python's logging module.
+        log.debug("Env %r has been stopped", self)
 
     def drivers(self):
         cdef void *drv = NULL
diff --git a/rasterio/_features.pyx b/rasterio/_features.pyx
index 89f9a3d..4c99012 100644
--- a/rasterio/_features.pyx
+++ b/rasterio/_features.pyx
@@ -1,19 +1,16 @@
+"""Feature extraction"""
 
 import logging
+
 import numpy as np
 cimport numpy as np
+
 from rasterio._io cimport InMemoryRaster
 from rasterio cimport _gdal, _ogr, _io
 from rasterio import dtypes
 
 
-log = logging.getLogger('rasterio')
-
-
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
+log = logging.getLogger(__name__)
 
 
 def _shapes(image, mask, connectivity, transform):
diff --git a/rasterio/_fill.pyx b/rasterio/_fill.pyx
index 4fb2c93..e9b11a9 100644
--- a/rasterio/_fill.pyx
+++ b/rasterio/_fill.pyx
@@ -1,6 +1,6 @@
 # distutils: language = c++
 # cython: profile=True
-#
+"""Raster fill."""
 
 import numpy as np
 cimport numpy as np
@@ -13,7 +13,7 @@ from rasterio._io cimport InMemoryRaster
 
 
 def _fillnodata(image, mask, double max_search_distance=100.0,
-        int smoothing_iterations=0):
+                int smoothing_iterations=0):
     cdef void *memdriver = _gdal.GDALGetDriverByName("MEM")
     cdef void *image_dataset = NULL
     cdef void *image_band = NULL
diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx
index 5af4e43..62581b8 100644
--- a/rasterio/_io.pyx
+++ b/rasterio/_io.pyx
@@ -1,4 +1,5 @@
 # cython: boundscheck=False
+"""Rasterio input/output."""
 
 from __future__ import absolute_import
 
@@ -20,16 +21,16 @@ from rasterio._drivers import driver_count, GDALEnv
 from rasterio._err import CPLErrors, GDALError, CPLE_OpenFailed
 from rasterio import dtypes
 from rasterio.coords import BoundingBox
-from rasterio.errors import DriverRegistrationError, RasterioIOError
+from rasterio.errors import (
+    DriverRegistrationError, RasterioIOError, NodataShadowWarning)
 from rasterio.five import text_type, string_types
 from rasterio.transform import Affine
 from rasterio.enums import ColorInterp, MaskFlags, Resampling
 from rasterio.sample import sample_gen
-from rasterio.vfs import parse_path
-from rasterio.warnings import NodataShadowWarning
+from rasterio.vfs import parse_path, vsi_path
 
 
-log = logging.getLogger('rasterio')
+log = logging.getLogger(__name__)
 
 
 cdef bint in_dtype_range(value, dtype):
@@ -860,9 +861,9 @@ cdef class RasterReader(_base.DatasetReader):
                 roff = 0
                 coff = 0
                 if window[0][0] < 0:
-                    roff = -window[0][0] * scaling_h
+                    roff = int(-window[0][0] * scaling_h)
                 if window[1][0] < 0:
-                    coff = -window[1][0] * scaling_w
+                    coff = int(-window[1][0] * scaling_w)
 
                 for dst, src in zip(
                         out if len(out.shape) == 3 else [out],
@@ -1199,12 +1200,9 @@ cdef class RasterReader(_base.DatasetReader):
 cdef class RasterUpdater(RasterReader):
     # Read-write access to raster data and metadata.
 
-    def __init__(
-            self, path, mode, driver=None,
-            width=None, height=None, count=None, 
-            crs=None, transform=None, dtype=None,
-            nodata=None,
-            **kwargs):
+    def __init__(self, path, mode, driver=None, width=None, height=None,
+                 count=None, crs=None, transform=None, dtype=None, nodata=None,
+                 **kwargs):
         # Validate write mode arguments.
         if mode == 'w':
             if not isinstance(driver, string_types):
@@ -1240,7 +1238,7 @@ cdef class RasterUpdater(RasterReader):
         self._dtypes = []
         self._nodatavals = []
         self._options = kwargs.copy()
-    
+
     def __repr__(self):
         return "<%s RasterUpdater name='%s' mode='%s'>" % (
             self.closed and 'closed' or 'open', 
@@ -1256,10 +1254,11 @@ cdef class RasterUpdater(RasterReader):
         cdef void *hband = NULL
         cdef int success
 
-        self.env = GDALEnv()
-        self.env.start()
-
+        # Parse the path to determine if there is scheme-specific
+        # configuration to be done.
         path, archive, scheme = parse_path(self.name)
+        path = vsi_path(path, archive=archive, scheme=scheme)
+
         if scheme and scheme != 'file':
             raise TypeError(
                 "VFS '{0}' datasets can not be created or updated.".format(
@@ -1288,7 +1287,6 @@ cdef class RasterUpdater(RasterReader):
                     drv = _gdal.GDALGetDriverByName(drv_name)
                     cple.check()
             except Exception as err:
-                self.env.stop()
                 raise DriverRegistrationError(str(err))
             
             # Find the equivalent GDAL data type or raise an exception
@@ -1334,7 +1332,6 @@ cdef class RasterUpdater(RasterReader):
                         gdal_dtype, options)
                     cple.check()
             except Exception as err:
-                self.env.stop()
                 if options != NULL:
                     _gdal.CSLDestroy(options)
                 raise
@@ -1363,7 +1360,6 @@ cdef class RasterUpdater(RasterReader):
                     self._hds = _gdal.GDALOpen(fname, 1)
                     cple.check()
             except CPLE_OpenFailed as err:
-                self.env.stop()
                 raise RasterioIOError(str(err))
 
         drv = _gdal.GDALGetDatasetDriver(self._hds)
@@ -1784,6 +1780,25 @@ cdef class RasterUpdater(RasterReader):
         cdef int *factors_c = NULL
         cdef const char *resampling_c = NULL
 
+        try:
+            # GDALBuildOverviews() takes a string algo name, not a 
+            # Resampling enum member (like warping) and accepts only
+            # a subset of the warp algorithms. 'NONE' is omitted below
+            # (what does that even mean?) and so is 'AVERAGE_MAGPHASE'
+            # (no corresponding member in the warp enum).
+            resampling_map = {
+                0: 'NEAREST',
+                2: 'CUBIC',
+                5: 'AVERAGE',
+                6: 'MODE',
+                7: 'GAUSS'}
+            resampling_alg = resampling_map[Resampling(resampling.value)]
+        except (KeyError, ValueError):
+            raise ValueError(
+                "resampling must be one of: {0}".format(", ".join(
+                    ['Resampling.{0}'.format(Resampling(k).name) for k in
+                     resampling_map.keys()])))
+
         # Allocate arrays.
         if factors:
             factors_c = <int *>_gdal.CPLMalloc(len(factors)*sizeof(int))
@@ -1791,7 +1806,7 @@ cdef class RasterUpdater(RasterReader):
                 factors_c[i] = factor
             try:
                 with CPLErrors() as cple:
-                    resampling_b = resampling.value.encode('utf-8')
+                    resampling_b = resampling_alg.encode('utf-8')
                     resampling_c = resampling_b
                     err = _gdal.GDALBuildOverviews(self._hds, resampling_c,
                         len(factors), factors_c, 0, NULL, NULL, NULL)
@@ -1911,14 +1926,16 @@ cdef class IndirectRasterUpdater(RasterUpdater):
         cdef void *hband = NULL
         cdef void *temp = NULL
         cdef int success
-        name_b = self.name.encode('utf-8')
+
+        # Parse the path to determine if there is scheme-specific
+        # configuration to be done.
+        path, archive, scheme = parse_path(self.name)
+        path = vsi_path(path, archive=archive, scheme=scheme)
+        name_b = path.encode('utf-8')
         cdef const char *fname = name_b
 
         memdrv = _gdal.GDALGetDriverByName("MEM")
 
-        self.env = GDALEnv()
-        self.env.start()
-        
         if self.mode == 'w':
             # Find the equivalent GDAL data type or raise an exception
             # We've mapped numpy scalar types to GDAL types so see
@@ -1940,7 +1957,6 @@ cdef class IndirectRasterUpdater(RasterUpdater):
                         gdal_dtype, NULL)
                     cple.check()
             except:
-                self.env.close()
                 raise
 
             if self._init_nodata is not None:
@@ -2223,7 +2239,7 @@ def windows_intersect(windows):
 
     def intersects(range1, range2):
         return not (
-            range1[0] > range2[1] or range1[1] < range2[0]
+            range1[0] >= range2[1] or range1[1] <= range2[0]
         )
 
     windows = np.array(windows)
diff --git a/rasterio/_warp.pyx b/rasterio/_warp.pyx
index fd8cb6d..937677f 100644
--- a/rasterio/_warp.pyx
+++ b/rasterio/_warp.pyx
@@ -1,4 +1,5 @@
 # distutils: language = c++
+"""Raster and vector warping and reprojection."""
 
 from enum import IntEnum
 import logging
@@ -8,8 +9,9 @@ cimport numpy as np
 
 from rasterio cimport _base, _gdal, _ogr, _io, _features
 from rasterio import dtypes
-from rasterio._err import CPLErrors, GDALError, CPLE_NotSupported
+from rasterio._err import CPLErrors, GDALError, CPLE_NotSupported, CPLE_AppDefined
 from rasterio._io cimport InMemoryRaster
+from rasterio.enums import Resampling
 from rasterio.errors import DriverRegistrationError, CRSError
 from rasterio.transform import Affine, from_bounds
 
@@ -41,19 +43,6 @@ cdef extern from "gdalwarper.h" nogil:
                                 double dfProgressScale=1.0)
 
 
-class Resampling(IntEnum):
-    nearest=0
-    bilinear=1
-    cubic=2
-    cubic_spline=3
-    lanczos=4
-    average=5
-    mode=6
-    max=8
-    min=9
-    med=10
-    q1=11
-    q3=12
 
 
 cdef extern from "ogr_geometry.h" nogil:
@@ -71,11 +60,7 @@ cdef extern from "ogr_spatialref.h":
         pass
 
 
-log = logging.getLogger('rasterio')
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
+log = logging.getLogger(__name__)
 
 
 def tastes_like_gdal(t):
@@ -579,6 +564,10 @@ def _calculate_default_transform(
             log.debug("Created transformer and warp output.")
         except CPLE_NotSupported as err:
             raise CRSError(err.errmsg)
+        except CPLE_AppDefined as err:
+            log.debug("Encountered points outside of valid dst crs region")
+            raise
+            #pass
         finally:
             if wkt != NULL:
                 _gdal.CPLFree(wkt)
diff --git a/rasterio/aws.py b/rasterio/aws.py
deleted file mode 100644
index 111064e..0000000
--- a/rasterio/aws.py
+++ /dev/null
@@ -1,61 +0,0 @@
-"""Amazon Web Service sessions and S3 raster access.
-
-Reuses concepts from awscli and boto including environment variable
-names and the .aws/config and /.aws/credentials files.
-
-Raster datasets on S3 may be accessed using ``aws.Session.open()``
-
-    from rasterio.aws import Session
-
-    with Session().open('s3://bucket/foo.tif') as src:
-        ...
-
-or by calling ``rasterio.open()`` from within a session block
-
-    with Session() as sess:
-        with rasterio.open('s3://bucket/foo.tif') as src:
-            ...
-"""
-
-import boto3
-
-from rasterio._drivers import ConfigEnv
-from rasterio.five import string_types
-
-
-class Session(ConfigEnv):
-    """A credentialed AWS S3 raster access session."""
-
-    def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
-                 aws_session_token=None, region_name=None, profile_name=None,
-                 **options):
-        self._session = boto3.Session(
-            aws_access_key_id=aws_access_key_id,
-            aws_secret_access_key=aws_secret_access_key,
-            aws_session_token=aws_session_token,
-            region_name=region_name,
-            profile_name=profile_name)
-        self._creds = self._session._session.get_credentials()
-        self.options = options.copy()
-        if self._creds.access_key:
-            self.options['AWS_ACCESS_KEY_ID'] = self._creds.access_key
-        if self._creds.secret_key:
-            self.options['AWS_SECRET_ACCESS_KEY'] = self._creds.secret_key
-        if self._creds.token:
-            self.options['AWS_SESSION_TOKEN'] = self._creds.token
-        if self._session.region_name:
-            self.options['AWS_REGION'] = self._session.region_name
-        self.prev_options = {}
-
-    def open(self, path, mode='r'):
-        """Read-only access to rasters on S3."""
-        if not isinstance(path, string_types):
-            raise TypeError("invalid path: %r" % path)
-        if mode == 'r-':
-            from rasterio._base import DatasetReader
-            s = DatasetReader(path, options=self.options)
-        else:
-            from rasterio._io import RasterReader
-            s = RasterReader(path, options=self.options)
-        s.start()
-        return s
diff --git a/rasterio/coords.py b/rasterio/coords.py
index 05beeba..15720ae 100644
--- a/rasterio/coords.py
+++ b/rasterio/coords.py
@@ -1,10 +1,12 @@
+"""Bounding box tuple, and disjoint operator."""
 
 from collections import namedtuple
 
 _BoundingBox = namedtuple('BoundingBox', ('left', 'bottom', 'right', 'top'))
 
+
 class BoundingBox(_BoundingBox):
-    """Bounding box named tuple, defining extent in cartesian coordinates
+    """Bounding box named tuple, defining extent in cartesian coordinates.
 
     .. code::
 
@@ -21,10 +23,12 @@ class BoundingBox(_BoundingBox):
     top :
         Top coordinate
     """
+
     pass
 
+
 def disjoint_bounds(bounds1, bounds2):
-    """Compare two bounds and determine if they are disjoint
+    """Compare two bounds and determine if they are disjoint.
 
     Parameters
     ----------
@@ -39,6 +43,5 @@ def disjoint_bounds(bounds1, bounds2):
     ``True`` if bounds are disjoint,
     ``False`` if bounds overlap
     """
-
     return (bounds1[0] > bounds2[2] or bounds1[2] < bounds2[0] or
             bounds1[1] > bounds2[3] or bounds1[3] < bounds2[1])
diff --git a/rasterio/crs.py b/rasterio/crs.py
index 5a04efe..45ac7d6 100644
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -1,21 +1,24 @@
-# Coordinate reference systems and functions.
-#
-# PROJ.4 is the law of this land: http://proj.osgeo.org/. But whereas PROJ.4
-# coordinate reference systems are described by strings of parameters such as
-#
-#   +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
-#
-# here we use mappings:
-#
-#   {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True}
-#
+"""Coordinate reference systems and functions.
+
+PROJ.4 is the law of this land: http://proj.osgeo.org/. But whereas PROJ.4
+coordinate reference systems are described by strings of parameters such as
+
+    +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
+
+here we use mappings:
+
+    {'proj': 'longlat', 'ellps': 'WGS84', 'datum': 'WGS84', 'no_defs': True}
+"""
 
 import json
+
 from rasterio._base import is_geographic_crs, is_projected_crs, is_same_crs
+from rasterio.errors import CRSError
 from rasterio.five import string_types
 
 
 def is_valid_crs(crs):
+    """Check if valid geographic or projected coordinate reference system."""
     return is_geographic_crs(crs) or is_projected_crs(crs)
 
 
@@ -50,13 +53,17 @@ def from_string(prjs):
 
     JSON text-encoded strings are allowed.
     """
-
     if '{' in prjs:
         # may be json, try to decode it
         try:
-            return json.loads(prjs, strict=False)
+            val = json.loads(prjs, strict=False)
         except ValueError:
-            raise ValueError('crs appears to be JSON but is not valid')
+            raise CRSError('crs appears to be JSON but is not valid')
+
+        if not val:
+            raise CRSError("crs is empty JSON")
+        else:
+            return val
 
     if prjs.strip().upper().startswith('EPSG:'):
         return from_epsg(prjs.split(':')[1])
@@ -82,7 +89,12 @@ def from_string(prjs):
         lambda kv: len(kv) == 2 and (kv[0], parse(kv[1])) or (kv[0], True),
         (p.split('=') for p in parts))
 
-    return dict((k, v) for k, v in items if k in all_proj_keys)
+    out = dict((k, v) for k, v in items if k in all_proj_keys)
+
+    if not out:
+        raise CRSError("crs is empty or invalid: {}".format(prjs))
+
+    return out
 
 
 def from_epsg(code):
diff --git a/rasterio/dtypes.py b/rasterio/dtypes.py
index 8b2de80..4f6c4d4 100644
--- a/rasterio/dtypes.py
+++ b/rasterio/dtypes.py
@@ -1,14 +1,14 @@
-# Mapping of GDAL to Numpy data types.
-#
-# Since 0.13 we are not importing numpy here and data types are strings.
-# Happily strings can be used throughout Numpy and so existing code will
-# break.
-#
-# Within Rasterio, to test data types, we use Numpy's dtype() factory to 
-# do something like this:
-#
-#   if np.dtype(destination.dtype) == np.dtype(rasterio.uint8): ...
-#
+"""Mapping of GDAL to Numpy data types.
+
+Since 0.13 we are not importing numpy here and data types are strings.
+Happily strings can be used throughout Numpy and so existing code will
+break.
+
+Within Rasterio, to test data types, we use Numpy's dtype() factory to
+do something like this:
+
+    if np.dtype(destination.dtype) == np.dtype(rasterio.uint8): ...
+"""
 
 bool_ = 'bool'
 ubyte = uint8 = 'uint8'
@@ -37,7 +37,7 @@ dtype_fwd = {
     8: complex_,        # GDT_CInt16
     9: complex_,        # GDT_CInt32
     10: complex64,      # GDT_CFloat32
-    11: complex128 }    # GDT_CFloat64
+    11: complex128}    # GDT_CFloat64
 
 dtype_rev = dict((v, k) for k, v in dtype_fwd.items())
 dtype_rev['uint8'] = 1
@@ -54,7 +54,7 @@ typename_fwd = {
     8: 'CInt16',
     9: 'CInt32',
     10: 'CFloat32',
-    11: 'CFloat64' }
+    11: 'CFloat64'}
 
 typename_rev = dict((v, k) for k, v in typename_fwd.items())
 
@@ -76,6 +76,7 @@ def _gdal_typename(dt):
 
 
 def check_dtype(dt):
+    """Check if dtype is a known dtype."""
     if dt not in dtype_rev:
         try:
             return dt().dtype.name in dtype_rev
@@ -85,10 +86,10 @@ def check_dtype(dt):
 
 
 def get_minimum_dtype(values):
-    """
+    """Determine minimum type to represent values.
+
     Uses range checking to determine the minimum integer or floating point
-    data type required
-    to represent values.
+    data type required to represent values.
 
     Parameters
     ----------
@@ -99,7 +100,6 @@ def get_minimum_dtype(values):
     -------
     rasterio dtype string
     """
-
     import numpy
 
     if not is_ndarray(values):
@@ -128,14 +128,14 @@ def get_minimum_dtype(values):
 
 
 def is_ndarray(array):
+    """Check if array is a ndarray."""
     import numpy
 
     return isinstance(array, numpy.ndarray) or hasattr(array, '__array__')
 
 
 def can_cast_dtype(values, dtype):
-    """
-    Tests if values can be cast to dtype without loss of information.
+    """Test if values can be cast to dtype without loss of information.
 
     Parameters
     ----------
@@ -147,7 +147,6 @@ def can_cast_dtype(values, dtype):
     boolean
         True if values can be cast to data type.
     """
-
     import numpy
 
     if not is_ndarray(values):
@@ -164,8 +163,7 @@ def can_cast_dtype(values, dtype):
 
 
 def validate_dtype(values, valid_dtypes):
-    """
-    Tests if dtype of values is one of valid_dtypes.
+    """Test if dtype of values is one of valid_dtypes.
 
     Parameters
     ----------
@@ -178,11 +176,10 @@ def validate_dtype(values, valid_dtypes):
     boolean:
         True if dtype of values is one of valid_dtypes
     """
-
     import numpy
 
     if not is_ndarray(values):
         values = numpy.array(values)
 
     return (values.dtype.name in valid_dtypes or
-            get_minimum_dtype(values) in valid_dtypes)
\ No newline at end of file
+            get_minimum_dtype(values) in valid_dtypes)
diff --git a/rasterio/enums.py b/rasterio/enums.py
index f62ffaa..ddcaa49 100644
--- a/rasterio/enums.py
+++ b/rasterio/enums.py
@@ -1,69 +1,85 @@
+"""Enumerations."""
 
 from enum import Enum, IntEnum
 
 
 class ColorInterp(IntEnum):
-    undefined=0
-    grey=1
-    gray=1
-    palette=2
-    red=3
-    green=4
-    blue=5
-    alpha=6
-    hue=7
-    saturation=8
-    lightness=9
-    cyan=10
-    magenta=11
-    yellow=12
-    black=13
-    Y=14
-    Cb=15
-    Cr=16
+    """Raster band color interpretation."""
+    undefined = 0
+    grey = 1
+    gray = 1
+    palette = 2
+    red = 3
+    green = 4
+    blue = 5
+    alpha = 6
+    hue = 7
+    saturation = 8
+    lightness = 9
+    cyan = 10
+    magenta = 11
+    yellow = 12
+    black = 13
+    Y = 14
+    Cb = 15
+    Cr = 16
 
 
-class Resampling(Enum):
-    nearest='NEAREST'
-    gauss='GAUSS'
-    cubic='CUBIC'
-    average='AVERAGE'
-    mode='MODE'
-    average_magphase='AVERAGE_MAGPHASE'
-    none='NONE'
+class Resampling(IntEnum):
+    """Available warp resampling algorithms.
+
+    The subset of 'nearest', 'cubic', 'average', 'mode', and 'gauss'
+    are available in making dataset overviews.
+
+    Note: 'gauss' is not available to the functions in rio.warp.
+    """
+    nearest = 0
+    bilinear = 1
+    cubic = 2
+    cubic_spline = 3
+    lanczos = 4
+    average = 5
+    mode = 6
+    gauss = 7
+    max = 8
+    min = 9
+    med = 10
+    q1 = 11
+    q3 = 12
 
 
 class Compression(Enum):
-    jpeg='JPEG'
-    lzw='LZW'
-    packbits='PACKBITS'
-    deflate='DEFLATE'
-    ccittrle='CCITTRLE'
-    ccittfax3='CCITTFAX3'
-    ccittfax4='CCITTFAX4'
-    lzma='LZMA'
-    none='NONE'
+    """Available compression algorithms."""
+    jpeg = 'JPEG'
+    lzw = 'LZW'
+    packbits = 'PACKBITS'
+    deflate = 'DEFLATE'
+    ccittrle = 'CCITTRLE'
+    ccittfax3 = 'CCITTFAX3'
+    ccittfax4 = 'CCITTFAX4'
+    lzma = 'LZMA'
+    none = 'NONE'
 
 
 class Interleaving(Enum):
-    pixel='PIXEL'
-    line='LINE'
-    band='BAND'
+    pixel = 'PIXEL'
+    line = 'LINE'
+    band = 'BAND'
 
 
 class MaskFlags(IntEnum):
-    all_valid=1
-    per_dataset=2
-    alpha=4
-    nodata=8
+    all_valid = 1
+    per_dataset = 2
+    alpha = 4
+    nodata = 8
 
 
 class PhotometricInterp(Enum):
-    black='MINISBLACK'
-    white='MINISWHITE'
-    rgb='RGB'
-    cmyk='CMYK'
-    ycbcr='YCbCr'
-    cielab='CIELAB'
-    icclab='ICCLAB'
-    itulab='ITULAB'
+    black = 'MINISBLACK'
+    white = 'MINISWHITE'
+    rgb = 'RGB'
+    cmyk = 'CMYK'
+    ycbcr = 'YCbCr'
+    cielab = 'CIELAB'
+    icclab = 'ICCLAB'
+    itulab = 'ITULAB'
diff --git a/rasterio/env.py b/rasterio/env.py
new file mode 100644
index 0000000..c5b6300
--- /dev/null
+++ b/rasterio/env.py
@@ -0,0 +1,188 @@
+"""Rasterio's GDAL/AWS environment"""
+
+import logging
+
+from rasterio._drivers import (
+    GDALEnv, del_gdal_config, get_gdal_config, set_gdal_config)
+from rasterio.dtypes import check_dtype
+from rasterio.errors import EnvError
+from rasterio.five import string_types
+from rasterio.transform import guard_transform
+from rasterio.vfs import parse_path, vsi_path
+
+
+# The currently active GDAL/AWS environment is a private attribute.
+_env = None
+
+log = logging.getLogger(__name__)
+
+
+class Env(object):
+    """Abstraction for GDAL and AWS configuration
+
+    The GDAL library is stateful: it has a registry of format drivers,
+    an error stack, and dozens of configuration options.
+
+    Rasterio's approach to working with GDAL is to wrap all the state
+    up using a Python context manager (see PEP 343,
+    https://www.python.org/dev/peps/pep-0343/). When the context is
+    entered GDAL drivers are registered, error handlers are
+    configured, and configuration options are set. When the context
+    is exited, drivers are removed from the registry and other
+    configurations are removed.
+
+    Example:
+
+        with Env(GDAL_CACHEMAX=512) as env:
+            # All drivers are registered, GDAL's raster block cache
+            # size is set to 512MB.
+            # Commence processing...
+            ...
+            # End of processing.
+
+        # At this point, configuration options are set to their
+        # previous (possible unset) values.
+
+    A boto3 session or boto3 session constructor arguments
+    `aws_access_key_id`, `aws_secret_access_key`, `aws_session_token`
+    may be passed to Env's constructor. In the latter case, a session
+    will be created as soon as needed. AWS credentials are configured
+    for GDAL as needed.
+    """
+
+    def __init__(self, aws_session=None, aws_access_key_id=None,
+                 aws_secret_access_key=None, aws_session_token=None,
+                 region_name=None, profile_name=None, **options):
+        """Create a new GDAL/AWS environment.
+
+        Note: this class is a context manager. GDAL isn't configured
+        until the context is entered via `with Env():`
+
+        Parameters
+        ----------
+        aws_session: object, optional
+            A boto3 session.
+        aws_access_key_id: string, optional
+            An access key id, as per boto3.
+        aws_secret_access_key: string, optional
+            A secret access key, as per boto3.
+        aws_session_token: string, optional
+            A session token, as per boto3.
+        region_name: string, optional
+            A region name, as per boto3.
+        profile_name: string, optional
+            A shared credentials profile name, as per boto3.
+        **options: optional
+            A mapping of GDAL configuration options, e.g.,
+            `CPL_DEBUG=True, CHECK_WITH_INVERT_PROJ=False`.
+
+        Returns
+        -------
+        A new instance of Env.
+
+        Note: We raise EnvError if the GDAL config options
+        AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY are given. AWS
+        credentials are handled exclusively by boto3.
+        """
+        if ('AWS_ACCESS_KEY_ID' in options or
+                'AWS_SECRET_ACCESS_KEY' in options):
+            raise EnvError(
+                "GDAL's AWS config options can not be directly set. "
+                "AWS credentials are handled exclusively by boto3.")
+        self.aws_access_key_id = aws_access_key_id
+        self.aws_secret_access_key = aws_secret_access_key
+        self.aws_session_token = aws_session_token
+        self.region_name = region_name
+        self.profile_name = profile_name
+        self.aws_session = aws_session
+        self._creds = (
+            self.aws_session._session.get_credentials()
+            if self.aws_session else None)
+        self.options = options.copy()
+        self.previous_options = {}
+        defenv()
+
+    def get_aws_credentials(self):
+        """Get credentials and configure GDAL."""
+        import boto3
+        options = {}
+        if not self.aws_session:
+            self.aws_session = boto3.Session(
+                aws_access_key_id=self.aws_access_key_id,
+                aws_secret_access_key=self.aws_secret_access_key,
+                aws_session_token=self.aws_session_token,
+                region_name=self.region_name,
+                profile_name=self.profile_name)
+        self._creds = self.aws_session._session.get_credentials()
+
+        # Pass these credentials to the GDAL environment.
+        if self._creds.access_key:  # pragma: no branch
+            options.update(aws_access_key_id=self._creds.access_key)
+        if self._creds.secret_key:  # pragma: no branch
+            options.update(aws_secret_access_key=self._creds.secret_key)
+        if self._creds.token:
+            options.update(aws_session_token=self._creds.token)
+        if self.aws_session.region_name:
+            options.update(aws_region=self.aws_session.region_name)
+
+        # Pass these credentials to the GDAL environment.
+        defenv()
+        global _env
+        _env.update_config_options(**options)
+
+    def drivers(self):
+        """Return a mapping of registered drivers."""
+        global _env
+        return _env.drivers()
+
+    def __enter__(self):
+        self.previous_options = getenv()
+        setenv(**self.options)
+        log.debug("Entering env %r context", self)
+        return self
+
+    def __exit__(self, exc_type=None, exc_val=None, exc_tb=None):
+        delenv()
+        setenv(**self.previous_options)
+        log.debug("Exiting env %r context", self)
+
+
+def defenv():
+    """Create a default environment if necessary."""
+    global _env
+    if _env:
+        log.debug("Environment %r exists", _env)
+    else:
+        _env = GDALEnv()
+        log.debug(
+            "New GDAL environment %r created", _env)
+
+
+def getenv():
+    """Get a mapping of current options."""
+    global _env
+    if not _env:
+        raise EnvError("No environment exists")
+    else:
+        log.debug("Got a copy of environment %r options", _env)
+        return _env.options.copy()
+
+
+def setenv(**options):
+    """Set options in the existing environment."""
+    global _env
+    if not _env:
+        raise EnvError("No environment exists")
+    else:
+        _env.update_config_options(**options)
+        log.debug("Updated existing %r with options %r", _env, options)
+
+
+def delenv():
+    """Delete options in the existing environment."""
+    global _env
+    if not _env:
+        raise EnvError("No environment exists")
+    else:
+        _env.clear_config_options()
+        log.debug("Cleared existing %r options", _env)
diff --git a/rasterio/errors.py b/rasterio/errors.py
index bb54e2c..d7b321b 100644
--- a/rasterio/errors.py
+++ b/rasterio/errors.py
@@ -1,4 +1,4 @@
-"""A module of errors."""
+"""Errors and Warnings."""
 
 from click import FileError
 
@@ -8,6 +8,11 @@ class CRSError(ValueError):
     to define a coordinate transformation."""
 
 
+class EnvError(Exception):
+    """Raised when the state of GDAL/AWS environment cannot be created
+    or modified."""
+
+
 class DriverRegistrationError(ValueError):
     """Raised when a format driver is requested but is not registered."""
 
@@ -16,9 +21,19 @@ class FileOverwriteError(FileError):
     """Raised when Rasterio's CLI refuses to clobber output files."""
 
     def __init__(self, message):
+        """Raise FileOverwriteError with message as hint."""
         super(FileOverwriteError, self).__init__('', hint=message)
 
 
 class RasterioIOError(IOError):
     """Raised when a dataset cannot be opened using one of the
     registered format drivers."""
+
+
+class NodataShadowWarning(Warning):
+    """Warn that a dataset's nodata attribute is shadowing its alpha band."""
+
+    def __str__(self):
+        return ("The dataset's nodata attribute is shadowing "
+                "the alpha band. All masks will be determined "
+                "by the nodata attribute")
diff --git a/rasterio/features.py b/rasterio/features.py
index 59e9c9b..0ba3d28 100644
--- a/rasterio/features.py
+++ b/rasterio/features.py
@@ -2,26 +2,19 @@
 
 from __future__ import absolute_import
 
-import json
 import logging
-import time
 import warnings
 
 import numpy as np
 
 import rasterio
 from rasterio._features import _shapes, _sieve, _rasterize, _bounds
+import rasterio.env
 from rasterio.transform import IDENTITY, guard_transform
 from rasterio.dtypes import validate_dtype, can_cast_dtype, get_minimum_dtype
 
 
-log = logging.getLogger('rasterio')
-
-
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
+log = logging.getLogger(__name__)
 
 
 def geometry_mask(
@@ -30,7 +23,9 @@ def geometry_mask(
         transform,
         all_touched=False,
         invert=False):
-    """Create a mask from shapes.  By default, mask is intended for use as a
+    """Create a mask from shapes.
+
+    By default, mask is intended for use as a
     numpy mask, where pixels that overlap shapes are False.
 
     Parameters
@@ -55,7 +50,6 @@ def geometry_mask(
     out : numpy ndarray of type 'bool'
         Result
     """
-
     fill, mask_value = (0, 1) if invert else (1, 0)
 
     return rasterize(
@@ -68,9 +62,7 @@ def geometry_mask(
 
 
 def shapes(image, mask=None, connectivity=4, transform=IDENTITY):
-    """
-    Return a generator of (polygon, value) for each each set of adjacent pixels
-    of the same value.
+    """Yield (polygon, value for each set of adjacent pixels of the same value.
 
     Parameters
     ----------
@@ -87,10 +79,10 @@ def shapes(image, mask=None, connectivity=4, transform=IDENTITY):
         If not provided, feature coordinates will be generated based on pixel
         coordinates
 
-    Returns
+    Yields
     -------
-    Generator of (polygon, value)
-        Yields a pair of (polygon, value) for each feature found in the image.
+    tuple
+        A pair of (polygon, value) for each feature found in the image.
         Polygons are GeoJSON-like dicts and the values are the associated value
         from the image, in the data type of the image.
         Note: due to floating point precision issues, values returned from a
@@ -105,19 +97,16 @@ def shapes(image, mask=None, connectivity=4, transform=IDENTITY):
     memory.
 
     """
-
     transform = guard_transform(transform)
-
-    with rasterio.drivers():
-        for s, v in _shapes(image, mask, connectivity, transform.to_gdal()):
-            yield s, v
+    rasterio.env.setenv()
+    for s, v in _shapes(image, mask, connectivity, transform.to_gdal()):
+        yield s, v
 
 
 def sieve(image, size, out=None, output=None, mask=None, connectivity=4):
-    """
-    Replaces small polygons in `image` with the value of their largest
-    neighbor.  Polygons are found for each set of neighboring pixels of the
-    same value.
+    """Replace small polygons in `image` with value of their largest neighbor.
+
+    Polygons are found for each set of neighboring pixels of the same value.
 
     Parameters
     ----------
@@ -154,7 +143,6 @@ def sieve(image, size, out=None, output=None, mask=None, connectivity=4):
     large amounts of memory.
 
     """
-
     # Start moving users over to 'out'.
     if output is not None:
         warnings.warn(
@@ -162,15 +150,15 @@ def sieve(image, size, out=None, output=None, mask=None, connectivity=4):
             "and will be removed before Rasterio 1.0.",
             FutureWarning,
             stacklevel=2)  # pragma: no cover
-    
+
     out = out if out is not None else output
 
     if out is None:
         out = np.zeros(image.shape, image.dtype)
 
-    with rasterio.drivers():
-        _sieve(image, size, out, mask, connectivity)
-        return out
+    rasterio.env.setenv()
+    _sieve(image, size, out, mask, connectivity)
+    return out
 
 
 def rasterize(
@@ -183,8 +171,7 @@ def rasterize(
         all_touched=False,
         default_value=1,
         dtype=None):
-    """
-    Returns an image array with input geometries burned in.
+    """Return an image array with input geometries burned in.
 
     Parameters
     ----------
@@ -226,7 +213,6 @@ def rasterize(
     rasterio.float64.
 
     """
-
     valid_dtypes = (
         'int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32', 'float64'
     )
@@ -239,7 +225,6 @@ def rasterize(
     def format_cast_error(param, dtype):
         return '{0} cannot be cast to specified dtype: {1}'.format(param, dtype)
 
-
     if fill != 0:
         fill_array = np.array([fill])
         if not validate_dtype(fill_array, valid_dtypes):
@@ -259,7 +244,6 @@ def rasterize(
     if dtype is not None and np.dtype(dtype).name not in valid_dtypes:
         raise ValueError(format_invalid_dtype('dtype'))
 
-
     valid_shapes = []
     shape_values = []
     for index, item in enumerate(shapes):
@@ -270,7 +254,7 @@ def rasterize(
             value = default_value
         geom = getattr(geom, '__geo_interface__', None) or geom
 
-        #not isinstance(geom, dict) or
+        # not isinstance(geom, dict) or
         if 'type' in geom or 'coordinates' in geom:
             valid_shapes.append((geom, value))
             shape_values.append(value)
@@ -299,7 +283,7 @@ def rasterize(
             "The 'output' keyword arg has been superseded by 'out' "
             "and will be removed before Rasterio 1.0.",
             FutureWarning,
-            stacklevel=2) # pragma: no cover
+            stacklevel=2)  # pragma: no cover
 
     out = out if out is not None else output
     if out is not None:
@@ -318,15 +302,16 @@ def rasterize(
 
     transform = guard_transform(transform)
 
-    with rasterio.drivers():
-        _rasterize(valid_shapes, out, transform.to_gdal(), all_touched)
+    rasterio.env.setenv()
+    _rasterize(valid_shapes, out, transform.to_gdal(), all_touched)
 
     return out
 
 
 def bounds(geometry):
-    """Returns a (minx, miny, maxx, maxy) bounding box.  From Fiona 1.4.8.
-    Modified to return bbox from geometry if available.
+    """Return a (minx, miny, maxx, maxy) bounding box.
+
+    From Fiona 1.4.8. Modified to return bbox from geometry if available.
 
     Parameters
     ----------
@@ -337,7 +322,6 @@ def bounds(geometry):
     tuple
         Bounding box: (minx, miny, maxx, maxy)
     """
-
     if 'bbox' in geometry:
         return tuple(geometry['bbox'])
 
diff --git a/rasterio/fill.py b/rasterio/fill.py
index 605add7..53811c0 100644
--- a/rasterio/fill.py
+++ b/rasterio/fill.py
@@ -1,14 +1,15 @@
+"""Fill holes in raster dataset by interpolation from the edges."""
+
 import rasterio
 from rasterio._fill import _fillnodata
-
+from rasterio.env import setenv
 
 def fillnodata(
         image,
         mask=None,
         max_search_distance=100.0,
         smoothing_iterations=0):
-    """
-    Fill holes in a raster dataset by interpolation from the edges.
+    """Fill holes in a raster dataset by interpolation from the edges.
 
     This algorithm will interpolate values for all designated nodata
     pixels (marked by zeros in `mask`). For each pixel a four direction
@@ -48,6 +49,6 @@ def fillnodata(
     """
     max_search_distance = float(max_search_distance)
     smoothing_iterations = int(smoothing_iterations)
-    with rasterio.drivers():
-        return _fillnodata(
-            image, mask, max_search_distance, smoothing_iterations)
+    rasterio.env.setenv()
+    return _fillnodata(
+        image, mask, max_search_distance, smoothing_iterations)
diff --git a/rasterio/five.py b/rasterio/five.py
index aa6268e..6d207b9 100644
--- a/rasterio/five.py
+++ b/rasterio/five.py
@@ -1,17 +1,20 @@
-# Python 2-3 compatibility
+"""Python 2-3 compatibility."""
 
 import itertools
 import sys
 
-if sys.version_info[0] >= 3:
+
+if sys.version_info[0] >= 3:   # pragma: no cover
     string_types = str,
     text_type = str
     integer_types = int,
     zip_longest = itertools.zip_longest
     import configparser
-else:
+    from urllib.parse import urlparse
+else:  # pragma: no cover
     string_types = basestring,
     text_type = unicode
     integer_types = int, long
     zip_longest = itertools.izip_longest
     import ConfigParser as configparser
+    from urlparse import urlparse
diff --git a/rasterio/mask.py b/rasterio/mask.py
index ec5423c..c1eb15b 100644
--- a/rasterio/mask.py
+++ b/rasterio/mask.py
@@ -1,3 +1,5 @@
+"""Mask the area outside of the input shapes with no data."""
+
 from __future__ import absolute_import
 
 import warnings
@@ -8,7 +10,8 @@ from rasterio.features import geometry_mask
 
 def mask(raster, shapes, nodata=None, crop=False, all_touched=False,
          invert=False):
-    """
+    """Mask the area outside of the input shapes with nodata.
+
     For all regions in the input raster outside of the regions defined by
     `shapes`, sets any data present to nodata.
 
@@ -43,7 +46,6 @@ def mask(raster, shapes, nodata=None, crop=False, all_touched=False,
         Information for mapping pixel coordinates in `masked` to another
         coordinate system.
     """
-
     if crop and invert:
         raise ValueError("crop and invert cannot both be True.")
     if nodata is None:
diff --git a/rasterio/merge.py b/rasterio/merge.py
index b073183..4551452 100644
--- a/rasterio/merge.py
+++ b/rasterio/merge.py
@@ -1,3 +1,5 @@
+"""Copy valid pixels from input files to an output file."""
+
 from __future__ import absolute_import
 
 import logging
@@ -6,12 +8,11 @@ import warnings
 
 import numpy as np
 
-import rasterio
-from rasterio._base import get_index, get_window
+from rasterio._base import get_window
 from rasterio.transform import Affine
 
 
-logger = logging.getLogger('rasterio')
+logger = logging.getLogger(__name__)
 
 
 def merge(sources, bounds=None, res=None, nodata=None, precision=7):
@@ -30,18 +31,18 @@ def merge(sources, bounds=None, res=None, nodata=None, precision=7):
 
     Parameters
     ----------
-    sources: list of source datasets 
+    sources: list of source datasets
         Open rasterio RasterReader objects to be merged.
-    bounds: tuple, optional 
+    bounds: tuple, optional
         Bounds of the output image (left, bottom, right, top).
-        If not set, bounds are determined from bounds of input rasters. 
-    res: tuple, optional 
+        If not set, bounds are determined from bounds of input rasters.
+    res: tuple, optional
         Output resolution in units of coordinate reference system. If not set,
         the resolution of the first raster is used. If a single value is passed,
         output pixels will be square.
     nodata: float, optional
         nodata value to use in output file. If not set, uses the nodata value
-        in the first input raster. 
+        in the first input raster.
 
     Returns
     -------
@@ -51,7 +52,6 @@ def merge(sources, bounds=None, res=None, nodata=None, precision=7):
         Information for mapping pixel coordinates in `dest` to another
         coordinate system
     """
-
     first = sources[0]
     first_res = first.res
     nodataval = first.nodatavals[0]
@@ -65,11 +65,11 @@ def merge(sources, bounds=None, res=None, nodata=None, precision=7):
         xs = []
         ys = []
         for src in sources:
-           left, bottom, right, top = src.bounds
-           xs.extend([left, right])
-           ys.extend([bottom, top])
+            left, bottom, right, top = src.bounds
+            xs.extend([left, right])
+            ys.extend([bottom, top])
         dst_w, dst_s, dst_e, dst_n = min(xs), min(ys), max(xs), max(ys)
-    
+
     logger.debug("Output bounds: %r", (dst_w, dst_s, dst_e, dst_n))
     output_transform = Affine.translation(dst_w, dst_n)
     logger.debug("Output transform, before scaling: %r", output_transform)
@@ -161,6 +161,6 @@ def merge(sources, bounds=None, res=None, nodata=None, precision=7):
         region = dest[:, roff:roff + trows, coff:coff + tcols]
         np.copyto(
             region, temp,
-            where=np.logical_and(region==nodataval, temp.mask==False))
+            where=np.logical_and(region == nodataval, temp.mask == False))
 
     return dest, output_transform
diff --git a/rasterio/plot.py b/rasterio/plot.py
index f6f0606..3646091 100644
--- a/rasterio/plot.py
+++ b/rasterio/plot.py
@@ -1,8 +1,10 @@
-"""Implementations of various common operations,
-like `show()` for displaying an array or with matplotlib.
+"""Implementations of various common operations.
+
+Including `show()` for displaying an array or with matplotlib.
 Most can handle a numpy array or `rasterio.Band()`.
 Primarily supports `$ rio insp`.
 """
+
 from __future__ import absolute_import
 
 import logging
@@ -12,9 +14,9 @@ import rasterio
 
 try:
     import matplotlib.pyplot as plt
-except ImportError:
+except ImportError:  # pragma: no cover
     plt = None
-except RuntimeError as e:
+except RuntimeError as e:  # pragma: no cover
     # Certain environment configurations can trigger a RuntimeError like:
 
     # Trying to import matplotlibRuntimeError: Python is not installed as a
@@ -26,11 +28,11 @@ except RuntimeError as e:
 
 from rasterio.five import zip_longest
 
-logger = logging.getLogger('rasterio')
+logger = logging.getLogger(__name__)
+
 
 def show(source, cmap='gray', with_bounds=True):
-    """
-    Display a raster or raster band using matplotlib.
+    """Display a raster or raster band using matplotlib.
 
     Parameters
     ----------
@@ -46,7 +48,6 @@ def show(source, cmap='gray', with_bounds=True):
         rather than pixel coordinates. Only works when source is
         (raster dataset, bidx).
     """
-
     if isinstance(source, tuple):
         arr = source[0].read(source[1])
         xs = source[0].res[0] / 2.
@@ -60,17 +61,15 @@ def show(source, cmap='gray', with_bounds=True):
         arr = source
         extent = None
     if plt is not None:
-        imax = plt.imshow(arr, cmap=cmap, extent=extent)
+        plt.imshow(arr, cmap=cmap, extent=extent)
         fig = plt.gcf()
         fig.show()
-    else:
+    else:  # pragma: no cover
         raise ImportError("matplotlib could not be imported")
 
 
 def show_hist(source, bins=10, masked=True, title='Histogram'):
-
-    """
-    Easily display a histogram with matplotlib.
+    """Easily display a histogram with matplotlib.
 
     Parameters
     ----------
@@ -85,8 +84,7 @@ def show_hist(source, bins=10, masked=True, title='Histogram'):
     title : str, optional
         Title for the figure.
     """
-
-    if plt is None:
+    if plt is None:  # pragma: no cover
         raise ImportError("Could not import matplotlib")
 
     if isinstance(source, (tuple, rasterio.Band)):
diff --git a/rasterio/profiles.py b/rasterio/profiles.py
index 3b2bedf..66982a5 100644
--- a/rasterio/profiles.py
+++ b/rasterio/profiles.py
@@ -9,18 +9,18 @@ class Profile:
     Subclasses will declare a format driver and driver-specific
     creation options.
     """
+
     driver = None
     defaults = {}
 
     def __call__(self, **kwargs):
-        """Returns a mapping of keyword args for writing a new datasets.
+        """Return a mapping of keyword args for writing a new datasets.
 
         Example:
 
             profile = SomeProfile()
             with rasterio.open('foo.tif', 'w', **profile()) as dst:
                 # Write data ...
-
         """
         if kwargs.get('driver', self.driver) != self.driver:
             raise ValueError(
@@ -33,6 +33,7 @@ class Profile:
 
 class DefaultGTiffProfile(Profile):
     """A tiled, band-interleaved, LZW-compressed, 8-bit GTiff profile."""
+
     driver = 'GTiff'
     defaults = {
         'interleave': 'band',
diff --git a/rasterio/rio/bounds.py b/rasterio/rio/bounds.py
index ab1f34a..c9b231f 100644
--- a/rasterio/rio/bounds.py
+++ b/rasterio/rio/bounds.py
@@ -10,6 +10,7 @@ from cligj import (
 
 from .helpers import write_features, to_lower
 import rasterio
+from rasterio.env import Env
 from rasterio.warp import transform_bounds
 
 logger = logging.getLogger('rio')
@@ -47,7 +48,6 @@ def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
     """
     import rasterio.warp
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    aws_session = (ctx.obj and ctx.obj.get('aws_session'))
     logger = logging.getLogger('rio')
     dump_kwds = {'sort_keys': True}
     if indent:
@@ -59,9 +59,10 @@ def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
     # This is the generator for (feature, bbox) pairs.
     class Collection(object):
 
-        def __init__(self):
+        def __init__(self, env):
             self._xs = []
             self._ys = []
+            self.env = env
 
         @property
         def bbox(self):
@@ -105,13 +106,10 @@ def bounds(ctx, input, precision, indent, compact, projection, dst_crs,
                 self._xs.extend(bbox[::2])
                 self._ys.extend(bbox[1::2])
 
-    col = Collection()
-    # Use the generator defined above as input to the generic output
-    # writing function.
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity > 2), aws_session:
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             write_features(
-                stdout, col, sequence=sequence,
+                stdout, Collection(env), sequence=sequence,
                 geojson_type=geojson_type, use_rs=use_rs,
                 **dump_kwds)
 
diff --git a/rasterio/rio/calc.py b/rasterio/rio/calc.py
index 4bc1227..30160a6 100644
--- a/rasterio/rio/calc.py
+++ b/rasterio/rio/calc.py
@@ -10,16 +10,17 @@ from cligj import files_inout_arg
 from .helpers import resolve_inout
 from . import options
 import rasterio
+from rasterio.env import Env
 from rasterio.fill import fillnodata
 from rasterio.features import sieve
 
 
 def get_bands(inputs, d, i=None):
     """Get a rasterio.Band object from calc's inputs"""
-    path = inputs[d] if d in dict(inputs) else inputs[int(d)-1][1]
+    path = inputs[d] if d in dict(inputs) else inputs[int(d) - 1][1]
     src = rasterio.open(path)
-    return (rasterio.band(src, i) if i else 
-            [rasterio.band(src, i) for i in src.indexes])
+    return (rasterio.band(src, i) if i else
+            [rasterio.band(src, j) for j in src.indexes])
 
 
 def read_array(ix, subix=None, dtype=None):
@@ -87,12 +88,11 @@ def calc(ctx, command, files, output, name, dtype, masked, force_overwrite,
     import numpy as np
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             output, files = resolve_inout(files=files, output=output,
-                force_overwrite=force_overwrite)
+                                          force_overwrite=force_overwrite)
 
             inputs = ([tuple(n.split('=')) for n in name] +
                       [(None, n) for n in files])
@@ -115,7 +115,7 @@ def calc(ctx, command, files, output, name, dtype, masked, force_overwrite,
                     #
                     # possibly something to do with the instance being
                     # a masked array.
-                    ctxkwds[name or '_i%d' % (i+1)] = src.read(masked=masked)
+                    ctxkwds[name or '_i%d' % (i + 1)] = src.read(masked=masked)
 
             # Extend snuggs.
             snuggs.func_map['read'] = read_array
@@ -145,6 +145,6 @@ def calc(ctx, command, files, output, name, dtype, masked, force_overwrite,
     except snuggs.ExpressionError as err:
         click.echo("Expression Error:")
         click.echo('  %s' % err.text)
-        click.echo(' ' +  ' ' * err.offset + "^")
+        click.echo(' ' + ' ' * err.offset + "^")
         click.echo(err)
         raise click.Abort()
diff --git a/rasterio/rio/clip.py b/rasterio/rio/clip.py
index 1717bae..6bacebd 100644
--- a/rasterio/rio/clip.py
+++ b/rasterio/rio/clip.py
@@ -7,6 +7,7 @@ from .helpers import resolve_inout
 from . import options
 import rasterio
 from rasterio.coords import disjoint_bounds
+from rasterio.env import Env
 
 
 # Clip command
@@ -54,9 +55,8 @@ def clip(
     from rasterio.warp import transform_bounds
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    aws_session = (ctx.obj and ctx.obj.get('aws_session'))
 
-    with rasterio.drivers(CPL_DEBUG=verbosity > 2), aws_session:
+    with Env(CPL_DEBUG=verbosity > 2) as env:
 
         output, files = resolve_inout(files=files, output=output)
         input = files[0]
diff --git a/rasterio/rio/convert.py b/rasterio/rio/convert.py
index 7937b4c..49925f3 100644
--- a/rasterio/rio/convert.py
+++ b/rasterio/rio/convert.py
@@ -9,6 +9,7 @@ import numpy as np
 from .helpers import resolve_inout
 from . import options
 import rasterio
+from rasterio.env import Env
 
 
 @click.command(short_help="Copy and convert raster dataset.")
@@ -55,9 +56,8 @@ def convert(
 
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
-    with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+    with Env(CPL_DEBUG=verbosity > 2) as env:
 
         outputfile, files = resolve_inout(files=files, output=output)
         inputfile = files[0]
diff --git a/rasterio/rio/edit_info.py b/rasterio/rio/edit_info.py
index e62bf22..3e7a188 100644
--- a/rasterio/rio/edit_info.py
+++ b/rasterio/rio/edit_info.py
@@ -8,6 +8,7 @@ import click
 from . import options
 import rasterio
 import rasterio.crs
+from rasterio.env import Env
 from rasterio.transform import guard_transform
 
 
@@ -118,7 +119,6 @@ def edit(ctx, input, nodata, crs, transform, tags, allmd, like):
     import numpy as np
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
     def in_dtype_range(value, dtype):
         infos = {'c': np.finfo, 'f': np.finfo, 'i': np.iinfo,
@@ -126,7 +126,7 @@ def edit(ctx, input, nodata, crs, transform, tags, allmd, like):
         rng = infos[np.dtype(dtype).kind](dtype)
         return rng.min <= value <= rng.max
 
-    with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+    with Env(CPL_DEBUG=(verbosity > 2)) as env:
 
         with rasterio.open(input, 'r+') as dst:
 
diff --git a/rasterio/rio/env.py b/rasterio/rio/env.py
index 79535ac..865ecde 100644
--- a/rasterio/rio/env.py
+++ b/rasterio/rio/env.py
@@ -6,6 +6,7 @@ import click
 
 import rasterio
 import rasterio.crs
+from rasterio.env import Env
 
 
 @click.command(short_help="Print information about the rio environment.")
@@ -17,9 +18,8 @@ def env(ctx, key):
     formats, etc.
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
     stdout = click.get_text_stream('stdout')
-    with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+    with Env(CPL_DEBUG=(verbosity > 2)) as env:
         if key == 'formats':
             for k, v in sorted(env.drivers().items()):
                 stdout.write("%s: %s\n" % (k, v))
diff --git a/rasterio/rio/info.py b/rasterio/rio/info.py
index 551b48e..c1184fe 100644
--- a/rasterio/rio/info.py
+++ b/rasterio/rio/info.py
@@ -1,13 +1,12 @@
 """Fetch and edit raster dataset metadata from the command line."""
 
 import json
-import logging
 
 import click
 
 from . import options
-import rasterio
 import rasterio.crs
+from rasterio.env import Env
 
 
 @click.command(short_help="Print information about a data file.")
@@ -62,13 +61,9 @@ def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
     Optionally print a single metadata item as a string.
     """
     verbosity = ctx.obj.get('verbosity')
-    aws_session = ctx.obj.get('aws_session')
-
-    logger = logging.getLogger('rio')
     mode = 'r' if (verbose or meta_member == 'stats') else 'r-'
     try:
-        with rasterio.drivers(
-                CPL_DEBUG=(verbosity > 2)), aws_session:
+        with Env(CPL_DEBUG=(verbosity > 2)):
             with rasterio.open(input, mode) as src:
                 info = src.profile
                 info['transform'] = info['affine'][:6]
@@ -109,5 +104,4 @@ def info(ctx, input, aspect, indent, namespace, meta_member, verbose, bidx,
                     click.echo(
                         json.dumps(src.tags(ns=namespace), indent=indent))
     except Exception:
-        logger.exception("Exception caught during processing")
         raise click.Abort()
diff --git a/rasterio/rio/insp.py b/rasterio/rio/insp.py
index 514ccc6..be51df7 100644
--- a/rasterio/rio/insp.py
+++ b/rasterio/rio/insp.py
@@ -12,14 +12,15 @@ import numpy
 import click
 
 from . import options
-from rasterio.plot import show, show_hist
 import rasterio
+from rasterio.env import Env
+from rasterio.plot import show, show_hist
 
 try:
     import matplotlib.pyplot as plt
-except ImportError:
+except ImportError:  # pragma: no cover
     plt = None
-except RuntimeError as e:
+except RuntimeError as e:  # pragma: no cover
     # Certain environment configurations can trigger a RuntimeError like:
 
     # Trying to import matplotlibRuntimeError: Python is not installed as a
@@ -52,7 +53,7 @@ def main(banner, dataset, alt_interpreter=None):
     local = dict(funcs, src=dataset, np=numpy, rio=rasterio, plt=plt)
     if not alt_interpreter:
         code.interact(banner, local=local)
-    elif alt_interpreter == 'ipython':
+    elif alt_interpreter == 'ipython':  # pragma: no cover
         import IPython
         IPython.InteractiveShell.banner1 = banner
         IPython.start_ipython(argv=[], user_ns=local)
@@ -77,10 +78,9 @@ def insp(ctx, input, mode, interpreter):
     """ Open the input file in a Python interpreter.
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    aws_session = (ctx.obj and ctx.obj.get('aws_session'))
     logger = logging.getLogger('rio')
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity > 2), aws_session:
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             with rasterio.open(input, mode) as src:
                 main(
                     'Rasterio %s Interactive Inspector (Python %s)\n'
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
index f173194..a8acec4 100644
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -16,33 +16,10 @@ import rasterio
 
 
 def configure_logging(verbosity):
-    log_level = max(10, 30 - 10*verbosity)
+    log_level = max(10, 30 - 10 * verbosity)
     logging.basicConfig(stream=sys.stderr, level=log_level)
 
 
-class FakeSession(object):
-    """Fake AWS Session."""
-
-    def __enter__(self):
-        pass
-
-    def __exit__(self, *args):
-        pass
-
-    def open(self, path, mode='r'):
-        return rasterio.open(path, mode)
-
-
-def get_aws_session(profile_name):
-    """Return a credentialed AWS session or a fake, depending on 
-    whether boto3 could be imported."""
-    try:
-        import rasterio.aws
-        return rasterio.aws.Session(profile_name=profile_name)
-    except ImportError:
-        return FakeSession()
-
-
 def gdal_version_cb(ctx, param, value):
     if not value or ctx.resilient_parsing:
         return
@@ -69,4 +46,3 @@ def main_group(ctx, verbose, quiet, aws_profile, gdal_version):
     configure_logging(verbosity)
     ctx.obj = {}
     ctx.obj['verbosity'] = verbosity
-    ctx.obj['aws_session'] = get_aws_session(aws_profile)
diff --git a/rasterio/rio/mask.py b/rasterio/rio/mask.py
index fd58c27..9f5e64f 100644
--- a/rasterio/rio/mask.py
+++ b/rasterio/rio/mask.py
@@ -8,6 +8,7 @@ import cligj
 from .helpers import resolve_inout
 from . import options
 import rasterio
+from rasterio.env import Env
 
 logger = logging.getLogger('rio')
 
@@ -71,7 +72,6 @@ def mask(
     from rasterio.features import bounds as calculate_bounds
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
     output, files = resolve_inout(
         files=files, output=output, force_overwrite=force_overwrite)
@@ -87,7 +87,7 @@ def mask(
         click.echo('Invert option ignored when using --crop', err=True)
         invert = False
 
-    with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+    with Env(CPL_DEBUG=verbosity > 2) as env:
         try:
             with click.open_file(geojson_mask) as fh:
                 geojson = json.loads(fh.read())
@@ -103,7 +103,6 @@ def mask(
         else:
             raise click.BadParameter('Invalid GeoJSON', param=input,
                                      param_hint='input')
-        bounds = geojson.get('bbox', calculate_bounds(geojson))
 
         with rasterio.open(input) as src:
             try:
@@ -115,7 +114,8 @@ def mask(
                     if crop:
                         raise click.BadParameter('not allowed for GeoJSON '
                                                  'outside the extent of the '
-                                                 'input raster',                                                                 param=crop,
+                                                 'input raster',
+                                                 param=crop,
                                                  param_hint='--crop')
 
             meta = src.meta.copy()
diff --git a/rasterio/rio/merge.py b/rasterio/rio/merge.py
index 5f167c0..97dce03 100644
--- a/rasterio/rio/merge.py
+++ b/rasterio/rio/merge.py
@@ -10,6 +10,7 @@ from cligj import files_inout_arg, format_opt
 from .helpers import resolve_inout
 from . import options
 import rasterio
+from rasterio.env import Env
 from rasterio.transform import Affine
 
 
@@ -27,7 +28,7 @@ from rasterio.transform import Affine
 @options.creation_options
 @click.pass_context
 def merge(ctx, files, output, driver, bounds, res, nodata, force_overwrite,
-        precision, creation_options):
+          precision, creation_options):
     """Copy valid pixels from input files to an output file.
 
     All files must have the same number of bands, data type, and
@@ -51,23 +52,23 @@ def merge(ctx, files, output, driver, bounds, res, nodata, force_overwrite,
     from rasterio.merge import merge as merge_tool
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
     output, files = resolve_inout(
         files=files, output=output, force_overwrite=force_overwrite)
 
-    sources = [rasterio.open(f) for f in files]
-    dest, output_transform = merge_tool(sources, bounds=bounds, res=res,
-                                        nodata=nodata, precision=precision)
+    with Env(CPL_DEBUG=verbosity > 2) as env:
+        sources = [rasterio.open(f) for f in files]
+        dest, output_transform = merge_tool(sources, bounds=bounds, res=res,
+                                            nodata=nodata, precision=precision)
 
-    profile = sources[0].profile
-    profile.pop('affine')
-    profile['transform'] = output_transform
-    profile['height'] = dest.shape[1]
-    profile['width'] = dest.shape[2]
-    profile['driver'] = driver
+        profile = sources[0].profile
+        profile.pop('affine')
+        profile['transform'] = output_transform
+        profile['height'] = dest.shape[1]
+        profile['width'] = dest.shape[2]
+        profile['driver'] = driver
 
-    profile.update(**creation_options)
+        profile.update(**creation_options)
 
-    with rasterio.open(output, 'w', **profile) as dst:
-        dst.write(dest)
+        with rasterio.open(output, 'w', **profile) as dst:
+            dst.write(dest)
diff --git a/rasterio/rio/overview.py b/rasterio/rio/overview.py
index 231e2d0..b9990e0 100644
--- a/rasterio/rio/overview.py
+++ b/rasterio/rio/overview.py
@@ -7,10 +7,10 @@ import operator
 
 import click
 
+from . import options
 import rasterio
 from rasterio.enums import Resampling
-
-from . import options
+from rasterio.env import Env
 
 
 def build_handler(ctx, param, value):
@@ -19,10 +19,10 @@ def build_handler(ctx, param, value):
             if '^' in value:
                 base, exp_range = value.split('^')
                 exp_min, exp_max = (int(v) for v in exp_range.split('..'))
-                value = [pow(int(base), k) for k in range(exp_min, exp_max+1)]
+                value = [pow(int(base), k) for k in range(exp_min, exp_max + 1)]
             else:
                 value = [int(v) for v in value.split(',')]
-        except Exception as exc:
+        except Exception:
             raise click.BadParameter(u"must match 'n,n,n,…' or 'n^n..n'.")
     return value
 
@@ -38,7 +38,8 @@ def build_handler(ctx, param, value):
 @click.option('--rebuild', help="Reconstruct existing overviews.",
               is_flag=True, default=False)
 @click.option('--resampling', help="Resampling algorithm.",
-              type=click.Choice([item.name for item in Resampling]),
+              type=click.Choice(
+                  [it.name for it in Resampling if it.value in [0, 2, 5, 6, 7]]),
               default='nearest', show_default=True)
 @click.pass_context
 def overview(ctx, input, build, ls, rebuild, resampling):
@@ -56,7 +57,7 @@ def overview(ctx, input, build, ls, rebuild, resampling):
 
       rio overview --build 2^1..4
 
-    Note that overviews can not currently be removed and are not 
+    Note that overviews can not currently be removed and are not
     automatically updated when the dataset's primary bands are
     modified.
 
@@ -67,9 +68,8 @@ def overview(ctx, input, build, ls, rebuild, resampling):
 
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
-    with rasterio.drivers(CPL_DEBUG=(verbosity > 2)) as env:
+    with Env(CPL_DEBUG=(verbosity > 2)) as env:
         with rasterio.open(input, 'r+') as dst:
 
             if ls:
diff --git a/rasterio/rio/rasterize.py b/rasterio/rio/rasterize.py
index 3807f20..dec0a6f 100644
--- a/rasterio/rio/rasterize.py
+++ b/rasterio/rio/rasterize.py
@@ -11,6 +11,8 @@ from . import options
 import rasterio
 from rasterio.transform import Affine
 from rasterio.coords import disjoint_bounds
+from rasterio.env import Env
+
 
 logger = logging.getLogger('rio')
 
@@ -126,7 +128,7 @@ def rasterize(
     if fill == int(fill):
         fill = int(fill)
 
-    with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+    with Env(CPL_DEBUG=verbosity > 2):
 
         def feature_value(feature):
             if prop and 'properties' in feature:
@@ -177,7 +179,7 @@ def rasterize(
                     ne = result != fill
                     data[ne] = result[ne]
                     data.mask[ne] = False
-                    out.write_band(bidx, data)
+                    out.write(data, indexes=bidx)
 
         else:
             if like is not None:
@@ -268,4 +270,4 @@ def rasterize(
             kwargs['nodata'] = fill
 
             with rasterio.open(output, 'w', **kwargs) as out:
-                out.write_band(1, result)
+                out.write(result, indexes=1)
diff --git a/rasterio/rio/sample.py b/rasterio/rio/sample.py
index 5ba0ea8..65656cc 100644
--- a/rasterio/rio/sample.py
+++ b/rasterio/rio/sample.py
@@ -4,6 +4,7 @@ import logging
 import click
 
 import rasterio
+from rasterio.env import Env
 
 
 @click.command(short_help="Sample a dataset.")
@@ -53,7 +54,6 @@ def sample(ctx, files, bidx):
 
     """
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    aws_session = (ctx.obj and ctx.obj.get('aws_session'))
     logger = logging.getLogger('rio')
 
     files = list(files)
@@ -67,7 +67,7 @@ def sample(ctx, files, bidx):
         points = [input]
 
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity > 2), aws_session:
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             with rasterio.open(source) as src:
                 if bidx is None:
                     indexes = src.indexes
diff --git a/rasterio/rio/shapes.py b/rasterio/rio/shapes.py
index beec86f..993eef8 100644
--- a/rasterio/rio/shapes.py
+++ b/rasterio/rio/shapes.py
@@ -7,6 +7,7 @@ import cligj
 from .helpers import coords, write_features
 from . import options
 import rasterio
+from rasterio.env import Env
 from rasterio.transform import Affine
 
 logger = logging.getLogger('rio')
@@ -92,7 +93,6 @@ def shapes(
     import rasterio.warp
 
     verbosity = ctx.obj['verbosity'] if ctx.obj else 1
-    aws_session = (ctx.obj and ctx.obj.get('aws_session'))
     logger = logging.getLogger('rio')
     dump_kwds = {'sort_keys': True}
     if indent:
@@ -108,9 +108,10 @@ def shapes(
     # This is the generator for (feature, bbox) pairs.
     class Collection(object):
 
-        def __init__(self):
+        def __init__(self, env):
             self._xs = []
             self._ys = []
+            self.env = env
 
         @property
         def bbox(self):
@@ -142,7 +143,7 @@ def shapes(
                         msk = src.read_masks(bidx)
                     else:
                         msk_shape = (
-                            src.height//sampling, src.width//sampling)
+                            src.height // sampling, src.width // sampling)
                         if bidx is None:
                             msk = numpy.zeros(
                                 (src.count,) + msk_shape, 'uint8')
@@ -162,7 +163,7 @@ def shapes(
                         img = src.read(bidx, masked=False)
                     else:
                         img = numpy.zeros(
-                            (src.height//sampling, src.width//sampling),
+                            (src.height // sampling, src.width // sampling),
                             dtype=src.dtypes[src.indexes.index(bidx)])
                         img = src.read(bidx, img, masked=False)
 
@@ -219,9 +220,9 @@ def shapes(
         geojson_type = 'collection'
 
     try:
-        with rasterio.drivers(CPL_DEBUG=(verbosity > 2)), aws_session:
+        with Env(CPL_DEBUG=(verbosity > 2)) as env:
             write_features(
-                stdout, Collection(), sequence=sequence,
+                stdout, Collection(env), sequence=sequence,
                 geojson_type=geojson_type, use_rs=use_rs,
                 **dump_kwds)
     except Exception:
diff --git a/rasterio/rio/stack.py b/rasterio/rio/stack.py
index f30d576..fc3e44d 100644
--- a/rasterio/rio/stack.py
+++ b/rasterio/rio/stack.py
@@ -1,4 +1,5 @@
 """Commands for operating on bands of datasets."""
+import collections
 import logging
 
 import click
@@ -7,6 +8,7 @@ from cligj import files_inout_arg, format_opt
 from .helpers import resolve_inout
 from . import options
 import rasterio
+from rasterio.env import Env
 from rasterio.five import zip_longest
 
 
@@ -57,9 +59,9 @@ def stack(ctx, files, output, driver, bidx, photometric, force_overwrite,
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 2
     logger = logging.getLogger('rio')
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity>2):
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             output, files = resolve_inout(files=files, output=output,
-                force_overwrite=force_overwrite)
+                                          force_overwrite=force_overwrite)
             output_count = 0
             indexes = []
             for path, item in zip_longest(files, bidx, fillvalue=None):
@@ -73,8 +75,8 @@ def stack(ctx, files, output, driver, bidx, photometric, force_overwrite,
                         lambda x: int(x) if x else None, item.split('..'))
                     if start is None:
                         start = 1
-                    indexes.append(src_indexes[slice(start-1, stop)])
-                    output_count += len(src_indexes[slice(start-1, stop)])
+                    indexes.append(src_indexes[slice(start - 1, stop)])
+                    output_count += len(src_indexes[slice(start - 1, stop)])
                 else:
                     parts = list(map(int, item.split(',')))
                     if len(parts) == 1:
@@ -105,9 +107,9 @@ def stack(ctx, files, output, driver, bidx, photometric, force_overwrite,
                             data = src.read(index)
                             dst.write(data, dst_idx)
                             dst_idx += 1
-                        elif isinstance(index, list):
+                        elif isinstance(index, collections.Iterable):
                             data = src.read(index)
-                            dst.write(data, range(dst_idx, dst_idx+len(index)))
+                            dst.write(data, range(dst_idx, dst_idx + len(index)))
                             dst_idx += len(index)
 
     except Exception:
diff --git a/rasterio/rio/transform.py b/rasterio/rio/transform.py
index bae7e5f..af4fa18 100644
--- a/rasterio/rio/transform.py
+++ b/rasterio/rio/transform.py
@@ -8,6 +8,7 @@ import click
 from cligj import precision_opt
 
 import rasterio
+from rasterio.env import Env
 
 
 @click.command(short_help="Transform coordinates.")
@@ -31,7 +32,7 @@ def transform(ctx, input, src_crs, dst_crs, precision):
         src = [input]
 
     try:
-        with rasterio.drivers(CPL_DEBUG=verbosity > 2):
+        with Env(CPL_DEBUG=verbosity > 2) as env:
             if src_crs.startswith('EPSG'):
                 src_crs = {'init': src_crs}
             elif os.path.exists(src_crs):
@@ -50,7 +51,7 @@ def transform(ctx, input, src_crs, dst_crs, precision):
                 if precision >= 0:
                     xs = [round(v, precision) for v in xs]
                     ys = [round(v, precision) for v in ys]
-                result = [0]*len(coords)
+                result = [0] * len(coords)
                 result[::2] = xs
                 result[1::2] = ys
                 print(json.dumps(result))
diff --git a/rasterio/rio/warp.py b/rasterio/rio/warp.py
index 8e13ec0..874d5e5 100644
--- a/rasterio/rio/warp.py
+++ b/rasterio/rio/warp.py
@@ -9,10 +9,11 @@ from .helpers import resolve_inout
 from . import options
 import rasterio
 from rasterio import crs
+from rasterio.env import Env
 from rasterio.errors import CRSError
 from rasterio.transform import Affine
-from rasterio.warp import (reproject, Resampling, calculate_default_transform,
-   transform_bounds)
+from rasterio.warp import (
+    reproject, Resampling, calculate_default_transform, transform_bounds)
 
 
 # Improper usage of rio-warp can lead to accidental creation of
@@ -25,7 +26,8 @@ MAX_OUTPUT_HEIGHT = 100000
 def bounds_handler(ctx, param, value):
     """Warn about future usage changes."""
     if value:
-        click.echo("Future Warning: "
+        click.echo(
+            "Future Warning: "
             "the semantics of the `--bounds` option will change in Rasterio "
             "version 1.0 from bounds of the source dataset to bounds of the "
             "destination dataset.", err=True)
@@ -35,7 +37,8 @@ def bounds_handler(ctx, param, value):
 def x_dst_bounds_handler(ctx, param, value):
     """Warn about future usage changes."""
     if value:
-        click.echo("Future Warning: "
+        click.echo(
+            "Future Warning: "
             "the `--x-dst-bounds` option will be removed in Rasterio version "
             "1.0 in favor of `--bounds`.", err=True)
     return value
@@ -121,7 +124,6 @@ def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
     """
 
     verbosity = (ctx.obj and ctx.obj.get('verbosity')) or 1
-    logger = logging.getLogger('rio')
 
     output, files = resolve_inout(
         files=files, output=output, force_overwrite=force_overwrite)
@@ -135,8 +137,8 @@ def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
         # Expand one value to two if needed
         res = (res[0], res[0]) if len(res) == 1 else res
 
-    with rasterio.drivers(CPL_DEBUG=verbosity > 2,
-                          CHECK_WITH_INVERT_PROJ=check_invert_proj):
+    with Env(CPL_DEBUG=verbosity > 2,
+             CHECK_WITH_INVERT_PROJ=check_invert_proj) as env:
         with rasterio.open(files[0]) as src:
             l, b, r, t = src.bounds
             out_kwargs = src.meta.copy()
@@ -157,7 +159,7 @@ def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds,
                     dst_height = template_ds.height
                     dst_width = template_ds.width
 
-            elif dst_crs:
+            elif dst_crs is not None:
                 try:
                     dst_crs = crs.from_string(dst_crs)
                 except ValueError as err:
diff --git a/rasterio/tool.py b/rasterio/tool.py
index fdf8639..3269290 100644
--- a/rasterio/tool.py
+++ b/rasterio/tool.py
@@ -2,28 +2,31 @@
 DEPRECATED; To be removed in 1.0
 """
 from __future__ import absolute_import
-import rasterio.rio.insp
 
 
 def show(*args, **kwargs):
+    import rasterio.plot
     import warnings
     warnings.warn("Deprecated; Use rasterio.rio.insp instead", DeprecationWarning)
-    return rasterio.rio.insp.show(*args, **kwargs)
+    return rasterio.plot.show_hist(*args, **kwargs)
 
 
-def stats(*args, **kwargs):
+def show_hist(*args, **kwargs):
+    import rasterio.plot
     import warnings
     warnings.warn("Deprecated; Use rasterio.rio.insp instead", DeprecationWarning)
-    return rasterio.rio.insp.stats(*args, **kwargs)
+    return rasterio.plot.show_hist(*args, **kwargs)
 
 
-def show_hist(*args, **kwargs):
+def stats(*args, **kwargs):
+    import rasterio.rio.insp
     import warnings
     warnings.warn("Deprecated; Use rasterio.rio.insp instead", DeprecationWarning)
-    return rasterio.rio.insp.show_hist(*args, **kwargs)
+    return rasterio.rio.insp.stats(*args, **kwargs)
 
 
-def main(*args, **kwargs):
+def main(*args, **kwargs):  # pragma: no cover
+    import rasterio.rio.insp
     import warnings
     warnings.warn("Deprecated; Use rasterio.rio.insp instead", DeprecationWarning)
     return rasterio.rio.insp.main(*args, **kwargs)
diff --git a/rasterio/transform.py b/rasterio/transform.py
index 46db1b1..36d4fbc 100644
--- a/rasterio/transform.py
+++ b/rasterio/transform.py
@@ -1,3 +1,5 @@
+"""Geospatial transforms"""
+
 from __future__ import absolute_import
 from __future__ import division
 
@@ -15,7 +17,7 @@ def tastes_like_gdal(seq):
 
 
 def guard_transform(transform):
-    """Return an Affine transformation instance"""
+    """Return an Affine transformation instance."""
     if not isinstance(transform, Affine):
         if tastes_like_gdal(transform):
             warnings.warn(
@@ -30,23 +32,32 @@ def guard_transform(transform):
 
 
 def from_origin(west, north, xsize, ysize):
-    """Return an Affine transformation for a georeferenced raster given
+    """Return an Affine transformation given upper left and pixel sizes.
+
+    Return an Affine transformation for a georeferenced raster given
     the coordinates of its upper left corner `west`, `north` and pixel
-    sizes `xsize`, `ysize`."""
+    sizes `xsize`, `ysize`.
+    """
     return Affine.translation(west, north) * Affine.scale(xsize, -ysize)
 
 
 def from_bounds(west, south, east, north, width, height):
-    """Return an Affine transformation for a georeferenced raster given
+    """Return an Affine transformation given bounds, width and height.
+
+    Return an Affine transformation for a georeferenced raster given
     its bounds `west`, `south`, `east`, `north` and its `width` and
-    `height` in number of pixels."""
+    `height` in number of pixels.
+    """
     return Affine.translation(west, north) * Affine.scale(
-            (east - west)/width, (south - north)/height)
+        (east - west) / width, (south - north) / height)
 
 
 def array_bounds(height, width, transform):
-    """Return the `west, south, east, north` bounds of an array given
-    its height, width, and an affine transform."""
+    """Return the bounds of an array given height, width, and a transform.
+
+    Return the `west, south, east, north` bounds of an array given
+    its height, width, and an affine transform.
+    """
     w, n = transform.xoff, transform.yoff
     e, s = transform * (width, height)
     return w, s, e, n
diff --git a/rasterio/vfs.py b/rasterio/vfs.py
index acaf555..4503d8c 100644
--- a/rasterio/vfs.py
+++ b/rasterio/vfs.py
@@ -1,31 +1,37 @@
-"""Implementation of Apache VFS schemes and URLs"""
+"""Implementation of Apache VFS schemes and URLs."""
 
 import os
 
+from rasterio.five import urlparse
+
 
 # NB: As not to propagate fallacies of distributed computing, Rasterio
 # does not support HTTP or FTP URLs via GDAL's vsicurl handler. Only
 # the following local filesystem schemes are supported.
 SCHEMES = {'gzip': 'gzip', 'zip': 'zip', 'tar': 'tar', 'https': 'curl',
-        'http': 'curl', 's3': 's3'}
+           'http': 'curl', 's3': 's3'}
 
 
 def parse_path(path, vfs=None):
     """Parse a file path or Apache VFS URL into its parts."""
     archive = scheme = None
     if vfs:
-        parts = vfs.split("://")
-        scheme = parts.pop(0) if parts else None
-        archive = parts.pop(0) if parts else None
+        parts = urlparse(vfs)
+        scheme = parts.scheme
+        archive = parts.path
+        if parts.netloc and parts.netloc != 'localhost':  # pragma: no cover
+            archive = parts.netloc + archive
     else:
-        parts = path.split("://")
-        path = parts.pop() if parts else None
-        scheme = parts.pop() if parts else None
+        parts = urlparse(path)
+        scheme = parts.scheme
+        path = parts.path
+        if parts.netloc and parts.netloc != 'localhost':
+            path = parts.netloc + path
         if scheme in SCHEMES:
             parts = path.split('!')
             path = parts.pop() if parts else None
             archive = parts.pop() if parts else None
-        elif scheme in (None, 'file'):
+        elif scheme in (None, '', 'file'):
             pass
         else:
             raise ValueError("VFS scheme {0} is unknown".format(scheme))
diff --git a/rasterio/warnings.py b/rasterio/warnings.py
deleted file mode 100644
index bdc22b4..0000000
--- a/rasterio/warnings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""Rasterio warnings."""
-
-
-class NodataShadowWarning(Warning):
-    """Warn that a dataset's nodata attribute is shadowing its alpha band"""
-    def __str__(self):
-        return ("The dataset's nodata attribute is shadowing "
-                "the alpha band. All masks will be determined "
-                "by the nodata attribute")
diff --git a/rasterio/warp.py b/rasterio/warp.py
index 81bfc9a..779b3bd 100644
--- a/rasterio/warp.py
+++ b/rasterio/warp.py
@@ -1,4 +1,4 @@
-"""Raster warping and reprojection"""
+"""Raster warping and reprojection."""
 
 from __future__ import absolute_import
 from __future__ import division
@@ -9,10 +9,11 @@ import warnings
 from affine import Affine
 import numpy as np
 
-import rasterio
 from rasterio._base import _transform
-from rasterio._warp import (_transform_geom, _reproject, Resampling,
-                            _calculate_default_transform)
+from rasterio._warp import (
+    _transform_geom, _reproject, _calculate_default_transform)
+from rasterio.enums import Resampling
+import rasterio.env
 from rasterio.transform import guard_transform
 
 
@@ -22,7 +23,8 @@ warnings.warn(
 
 
 def transform(src_crs, dst_crs, xs, ys, zs=None):
-    """
+    """Transform vectors from source to target coordinate reference system.
+
     Transform vectors of x, y and optionally z from source
     coordinate reference system into target.
 
@@ -46,6 +48,7 @@ def transform(src_crs, dst_crs, xs, ys, zs=None):
     Tuple of x, y, and optionally z vectors, transformed into the target
     coordinate reference system.
     """
+    rasterio.env.setenv()
     return _transform(src_crs, dst_crs, xs, ys, zs)
 
 
@@ -56,8 +59,7 @@ def transform_geom(
         antimeridian_cutting=False,
         antimeridian_offset=10.0,
         precision=-1):
-    """
-    Transform geometry from source coordinate reference system into target.
+    """Transform geometry from source coordinate reference system into target.
 
     Parameters
     ------------
@@ -84,7 +86,7 @@ def transform_geom(
     out: GeoJSON like dict object
         Transformed geometry in GeoJSON dict format
     """
-
+    rasterio.env.setenv()
     return _transform_geom(
         src_crs,
         dst_crs,
@@ -94,11 +96,18 @@ def transform_geom(
         precision)
 
 
-def transform_bounds(src_crs, dst_crs, left, bottom, right, top, densify_pts=21):
-    """
-    Transforms bounds from src_crs to dst_crs, optionally densifying the edges
-    (to account for nonlinear transformations along these edges) and extracting
-    the outermost bounds.
+def transform_bounds(
+        src_crs,
+        dst_crs,
+        left,
+        bottom,
+        right,
+        top,
+        densify_pts=21):
+    """Transform bounds from src_crs to dst_crs.
+
+    Optionally densifying the edges (to account for nonlinear transformations
+    along these edges) and extracting the outermost bounds.
 
     Note: this does not account for the antimeridian.
 
@@ -121,7 +130,6 @@ def transform_bounds(src_crs, dst_crs, left, bottom, right, top, densify_pts=21)
     left, bottom, right, top: float
         Outermost coordinates in target coordinate reference system.
     """
-
     if densify_pts < 0:
         raise ValueError('densify parameter must be >= 0')
 
@@ -135,14 +143,14 @@ def transform_bounds(src_crs, dst_crs, left, bottom, right, top, densify_pts=21)
         for x in (left, right):
             in_xs.extend([x] * (densify_pts + 2))
             in_ys.extend(
-                bottom + np.arange(0, densify_pts + 2, dtype=np.float32)
-                * ((top - bottom) * densify_factor)
+                bottom + np.arange(0, densify_pts + 2, dtype=np.float32) *
+                ((top - bottom) * densify_factor)
             )
 
         for y in (bottom, top):
             in_xs.extend(
-                left + np.arange(1, densify_pts + 1, dtype=np.float32)
-                * ((right - left) * densify_factor)
+                left + np.arange(1, densify_pts + 1, dtype=np.float32) *
+                ((right - left) * densify_factor)
             )
             in_ys.extend([y] * densify_pts)
 
@@ -223,15 +231,23 @@ def reproject(
     out: None
         Output is written to destination.
     """
-
     # Resampling guard.
-    _ = Resampling(resampling)
+    try:
+        Resampling(resampling)
+        if resampling == 7:
+            raise ValueError
+    except ValueError:
+        raise ValueError(
+            "resampling must be one of: {0}".format(", ".join(
+                ['Resampling.{0}'.format(k) for k in
+                 Resampling.__members__.keys() if k != 'gauss'])))
 
     if src_transform:
         src_transform = guard_transform(src_transform).to_gdal()
     if dst_transform:
         dst_transform = guard_transform(dst_transform).to_gdal()
 
+    rasterio.env.setenv()
     _reproject(
         source,
         destination,
@@ -255,7 +271,8 @@ def calculate_default_transform(
         right,
         top,
         resolution=None):
-    """
+    """Calculate parameters for reproject function.
+
     Transforms bounds to destination coordinate system, calculates resolution
     if not provided, and returns destination transform and dimensions.
     Intended to be used to calculate parameters for reproject function.
@@ -287,7 +304,7 @@ def calculate_default_transform(
 
     Note
     ----
-    Must be called within a raster.drivers() context
+    Should be called within a raster.env.Env() context
 
     Some behavior of this function is determined by the
     CHECK_WITH_INVERT_PROJ environment variable
@@ -295,6 +312,7 @@ def calculate_default_transform(
              avoids visual artifacts and coordinate discontinuties.
         NO:  reproject coordinates beyond valid bound limits
     """
+    rasterio.env.setenv()
     dst_affine, dst_width, dst_height = _calculate_default_transform(
         src_crs, dst_crs,
         width, height,
diff --git a/rasterio/windows.py b/rasterio/windows.py
index 0950452..7dc2136 100644
--- a/rasterio/windows.py
+++ b/rasterio/windows.py
@@ -1,7 +1,24 @@
+"""Windows and related functions."""
 
-def get_data_window(arr, nodata=None):
+import functools
+import collections
+
+
+def iter_args(function):
+    """Decorator to allow function to take either *args or
+    a single iterable which gets expanded to *args.
     """
-    Returns a window for the non-nodata pixels within the input array.
+    @functools.wraps(function)
+    def wrapper(*args, **kwargs):
+        if len(args) == 1 and isinstance(args[0], collections.Iterable):
+            return function(*args[0])
+        else:
+            return function(*args)
+    return wrapper
+
+
+def get_data_window(arr, nodata=None):
+    """Return a window for the non-nodata pixels within the input array.
 
     Parameters
     ----------
@@ -17,14 +34,13 @@ def get_data_window(arr, nodata=None):
     ((row_start, row_stop), (col_start, col_stop))
 
     """
-
     from rasterio._io import get_data_window
     return get_data_window(arr, nodata)
 
 
-def union(windows):
-    """
-    Union windows and return the outermost extent they cover.
+ at iter_args
+def union(*windows):
+    """Union windows and return the outermost extent they cover.
 
     Parameters
     ----------
@@ -35,14 +51,13 @@ def union(windows):
     -------
     ((row_start, row_stop), (col_start, col_stop))
     """
-
     from rasterio._io import window_union
     return window_union(windows)
 
 
-def intersection(windows):
-    """
-    Intersect windows and return the innermost extent they cover.
+ at iter_args
+def intersection(*windows):
+    """Intersect windows and return the innermost extent they cover.
 
     Will raise ValueError if windows do not intersect.
 
@@ -55,14 +70,13 @@ def intersection(windows):
     -------
     ((row_start, row_stop), (col_start, col_stop))
     """
-
     from rasterio._io import window_intersection
     return window_intersection(windows)
 
 
-def intersect(windows):
-    """
-    Test if windows intersect.
+ at iter_args
+def intersect(*windows):
+    """Test if windows intersect.
 
     Parameters
     ----------
@@ -74,6 +88,5 @@ def intersect(windows):
     boolean:
         True if all windows intersect.
     """
-
     from rasterio._io import windows_intersect
     return windows_intersect(windows)
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 6a13aa3..8651db1 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,10 +1,12 @@
-affine
+affine>=1.3.0
+boto3>=1.2.4
 cligj
 cython>=0.23.4
 delocate
 enum34
 numpy>=1.10
 snuggs>=1.2
+packaging
 pytest>=2.8.2
 pytest-cov>=2.2.0
 setuptools>=0.9.8
diff --git a/requirements.txt b/requirements.txt
index 9071810..b2cee4f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
-affine
+affine>=1.3.0
+boto3>=1.2.4
 cligj
 enum34
 numpy>=1.8
diff --git a/scripts/travis_gdal_install.sh b/scripts/travis_gdal_install.sh
index 1e289b3..7e153e7 100644
--- a/scripts/travis_gdal_install.sh
+++ b/scripts/travis_gdal_install.sh
@@ -91,9 +91,9 @@ fi
 
 if [ ! -d $GDALINST/gdal-2.1.0 ]; then
   cd $GDALBUILD
-  wget http://download.osgeo.org/gdal/2.1.0beta1/gdal-2.1.0beta1.tar.gz
-  tar -xzf gdal-2.1.0beta1.tar.gz
-  cd gdal-2.1.0beta1
+  wget http://download.osgeo.org/gdal/2.1.0/gdal-2.1.0.tar.gz
+  tar -xzf gdal-2.1.0.tar.gz
+  cd gdal-2.1.0
   ./configure --prefix=$GDALINST/gdal-2.1.0 $GDALOPTS
   make -s -j 2
   make install
diff --git a/setup.py b/setup.py
index c90a773..40ea6f9 100755
--- a/setup.py
+++ b/setup.py
@@ -74,7 +74,8 @@ include_dirs = []
 library_dirs = []
 libraries = []
 extra_link_args = []
-gdal_output = [None]*3
+gdal2plus = False
+gdal_output = [None] * 4
 
 try:
     import numpy
@@ -85,7 +86,7 @@ except ImportError:
 
 try:
     gdal_config = os.environ.get('GDAL_CONFIG', 'gdal-config')
-    for i, flag in enumerate(("--cflags", "--libs", "--datadir")):
+    for i, flag in enumerate(("--cflags", "--libs", "--datadir", "--version")):
         gdal_output[i] = check_output([gdal_config, flag]).strip()
 
     for item in gdal_output[0].split():
@@ -99,6 +100,9 @@ try:
         else:
             # e.g. -framework GDAL
             extra_link_args.append(item)
+    # datadir, gdal_output[2] handled below
+    for item in gdal_output[3].split():
+        gdal2plus = not item.startswith("1.")
 
 except Exception as e:
     if os.name == "nt":
@@ -148,6 +152,14 @@ if os.environ.get('CYTHON_COVERAGE'):
 
 log.debug('ext_options:\n%s', pprint.pformat(ext_options))
 
+if gdal2plus:
+    # GDAL>=2.0 does not require vendorized rasterfill.cpp
+    cython_fill = ['rasterio/_fill.pyx']
+    sdist_fill = ['rasterio/_fill.cpp']
+else:
+    cython_fill = ['rasterio/_fill.pyx', 'rasterio/rasterfill.cpp']
+    sdist_fill = ['rasterio/_fill.cpp', 'rasterio/rasterfill.cpp']
+
 # When building from a repo, Cython is required.
 if os.path.exists("MANIFEST.in") and "clean" not in sys.argv:
     log.info("MANIFEST.in found, presume a repo, cythonizing...")
@@ -170,12 +182,12 @@ if os.path.exists("MANIFEST.in") and "clean" not in sys.argv:
         Extension(
             'rasterio._warp', ['rasterio/_warp.pyx'], **ext_options),
         Extension(
-            'rasterio._fill', ['rasterio/_fill.pyx', 'rasterio/rasterfill.cpp'], **ext_options),
+            'rasterio._fill', cython_fill, **ext_options),
         Extension(
             'rasterio._err', ['rasterio/_err.pyx'], **ext_options),
         Extension(
-            'rasterio._example', ['rasterio/_example.pyx'], **ext_options),
-        ], quiet=True, **cythonize_options)
+            'rasterio._example', ['rasterio/_example.pyx'], **ext_options)],
+        quiet=True, **cythonize_options)
 
 # If there's no manifest template, as in an sdist, we just specify .c files.
 else:
@@ -193,12 +205,11 @@ else:
         Extension(
             'rasterio._warp', ['rasterio/_warp.cpp'], **ext_options),
         Extension(
-            'rasterio._fill', ['rasterio/_fill.cpp', 'rasterio/rasterfill.cpp'], **ext_options),
+            'rasterio._fill', sdist_fill, **ext_options),
         Extension(
             'rasterio._err', ['rasterio/_err.c'], **ext_options),
         Extension(
-            'rasterio._example', ['rasterio/_example.c'], **ext_options),
-            ]
+            'rasterio._example', ['rasterio/_example.c'], **ext_options)]
 
 with open('README.rst') as f:
     readme = f.read()
@@ -263,8 +274,8 @@ setup_args = dict(
     install_requires=inst_reqs,
     extras_require={
         'ipython': ['ipython>=2.0'],
-        's3': ['boto3'],
-        'test': ['boto3', 'packaging']})
+        's3': ['boto3>=1.2.4'],
+        'test': ['boto3>=1.2.4', 'packaging']})
 
 if os.environ.get('PACKAGE_DATA'):
     setup_args['package_data'] = {'rasterio': ['gdal_data/*', 'proj_data/*']}
diff --git a/tests/README.rst b/tests/README.rst
new file mode 100644
index 0000000..d0314f0
--- /dev/null
+++ b/tests/README.rst
@@ -0,0 +1,20 @@
+=====
+Tests
+=====
+
+From the root of the project, run
+
+.. code-block::
+
+  $ python -m pytest
+
+The tests that require Amazon S3 access will be skipped if you have no credentials. You can test using a
+key like so:
+
+.. code-block::
+
+  $ AWS_ACCESS_KEY_ID=ID AWS_SECRET_ACCESS_KEY=KEY python -m pytest
+
+The key used for Travis is generated using the Amazon CloudFormation template at
+https://github.com/mapbox/rasterio/blob/master/cloudformation/testuser.json. If you had to fork
+Rasterio and run your own tests, you could `use this template <http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-create-stack.html>`__ to create your own IAM user and get a new key from your stack's "Outputs" field.
diff --git a/tests/conftest.py b/tests/conftest.py
index e7e7154..cdc1770 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -17,16 +17,18 @@ if sys.version_info > (3,):
     reduce = functools.reduce
 
 test_files = [os.path.join(os.path.dirname(__file__), p) for p in [
-    'data/RGB.byte.tif', 'data/float.tif', 'data/float_nan.tif', 'data/shade.tif']]
+    'data/RGB.byte.tif', 'data/float.tif', 'data/float_nan.tif',
+    'data/shade.tif']]
 
 
 def pytest_cmdline_main(config):
-    # Bail if the test raster data is not present. Test data is not 
+    # Bail if the test raster data is not present. Test data is not
     # distributed with sdists since 0.12.
     if reduce(operator.and_, map(os.path.exists, test_files)):
         print("Test data present.")
     else:
-        print("Test data not present. See download directions in tests/data/README.rst")
+        print("Test data not present. See download directions in "
+              "tests/data/README.rst")
         sys.exit(1)
 
 
@@ -146,8 +148,8 @@ def pixelated_image(basic_image):
     """
 
     image = basic_image.copy()
-    image [0, 0] = 1
-    image [8, 8] = 1
+    image[0, 0] = 1
+    image[8, 8] = 1
 
     return image
 
@@ -197,9 +199,8 @@ def basic_image_file(tmpdir, basic_image):
         "height": image.shape[0],
         "nodata": None
     }
-    with rasterio.drivers():
-        with rasterio.open(outfilename, 'w', **kwargs) as out:
-            out.write_band(1, image)
+    with rasterio.open(outfilename, 'w', **kwargs) as out:
+        out.write(image, indexes=1)
 
     return outfilename
 
@@ -233,8 +234,18 @@ def pixelated_image_file(tmpdir, pixelated_image):
         "height": image.shape[0],
         "nodata": 255
     }
-    with rasterio.drivers():
-        with rasterio.open(outfilename, 'w', **kwargs) as out:
-            out.write_band(1, image)
+    with rasterio.open(outfilename, 'w', **kwargs) as out:
+        out.write(image, indexes=1)
 
     return outfilename
+
+
+ at pytest.fixture(scope='function')
+def gdalenv(request):
+    import rasterio.env
+
+    def fin():
+        if rasterio.env._env:
+            rasterio.env.delenv()
+            rasterio.env._env = None
+    request.addfinalizer(fin)
diff --git a/tests/data/alpha.tif b/tests/data/alpha.tif
new file mode 100644
index 0000000..5e2ed01
Binary files /dev/null and b/tests/data/alpha.tif differ
diff --git a/tests/data/world.rgb.tif b/tests/data/world.rgb.tif
new file mode 100644
index 0000000..ed695e1
Binary files /dev/null and b/tests/data/world.rgb.tif differ
diff --git a/tests/test_aws.py b/tests/test_aws.py
deleted file mode 100644
index 42a8efb..0000000
--- a/tests/test_aws.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# Tests requiring S3 credentials.
-# Collected here to make them easier to skip/xfail.
-
-import logging
-import sys
-
-from packaging.version import parse
-import pytest
-
-import rasterio
-from rasterio.aws import Session
-from rasterio.rio.main import main_group
-
-
-# Custom markers.
-mingdalversion = pytest.mark.skipif(
-    parse(rasterio.__gdal_version__) < parse('2.1.0dev'),
-    reason="S3 raster access requires GDAL 2.1")
-
-credentials = pytest.mark.skipif(
-    not(Session()._creds), reason="S3 raster access requires credentials")
-
-
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
-
-L8TIF = "s3://landsat-pds/L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF"
-
-
-def test_session():
-    """Create a session with arguments."""
-    s = Session(aws_access_key_id='id', aws_secret_access_key='key',
-                aws_session_token='token', region_name='null-island-1')
-    assert s._creds.access_key == 'id'
-    assert s._creds.secret_key == 'key'
-    assert s._creds.token == 'token'
-    assert s._session.region_name == 'null-island-1'
-
-
-def test_session_env(monkeypatch):
-    """Create a session with env vars."""
-    monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'id')
-    monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'key')
-    monkeypatch.setenv('AWS_SESSION_TOKEN', 'token')
-    s = Session()
-    assert s._creds.access_key == 'id'
-    assert s._creds.secret_key == 'key'
-    assert s._creds.token == 'token'
-    monkeypatch.undo()
-
-
- at mingdalversion
- at credentials
-def test_with_session():
-    """Enter and exit a session."""
-    with Session():
-        with rasterio.open(L8TIF) as f:
-            assert f.count == 1
-
-
- at mingdalversion
- at credentials
-def test_open_with_session():
-    """Enter and exit a session."""
-    s = Session()
-    with s.open(L8TIF) as f:
-        assert f.count == 1
-
-
- at mingdalversion
- at credentials
-def test_open_with_session_minus_mode():
-    """Enter and exit a session, reading in 'r-' mode"""
-    s = Session()
-    with s.open(L8TIF, 'r-') as f:
-        assert f.count == 1
-
-
-# CLI tests.
-
- at mingdalversion
- at credentials
-def test_rio_info(runner):
-    """S3 is supported by rio-info"""
-    result = runner.invoke(main_group, ['info', L8TIF])
-    assert result.exit_code == 0
-    assert '"crs": "EPSG:32645"' in result.output
-
-
- at mingdalversion
- at credentials
-def test_rio_insp(runner):
-    """S3 is supported by rio-insp"""
-    result = runner.invoke(main_group, ['insp', L8TIF])
-    assert result.exit_code == 0
-    assert 'Interactive Inspector' in result.output
-
-
- at mingdalversion
- at credentials
-def test_rio_bounds(runner):
-    """S3 is supported by rio-bounds"""
-    result = runner.invoke(main_group, ['bounds', '--bbox', L8TIF])
-    assert result.exit_code == 0
-    assert '[85.8' in result.output
-
-
- at mingdalversion
- at credentials
-def test_rio_shapes(runner):
-    """S3 is supported by rio-shapes"""
-    result = runner.invoke(
-        main_group, ['shapes', '--as-mask', '--sampling', '16', L8TIF])
-    assert result.exit_code == 0
-    assert 'FeatureCollection' in result.output
-
-
- at mingdalversion
- at credentials
-def test_rio_sample(runner):
-    """S3 is supported by rio-sample"""
-    result = runner.invoke(
-        main_group, ['sample', L8TIF], input="[420000, 2350000]")
-    assert result.exit_code == 0
-    assert '[10680]' in result.output
-
-
- at mingdalversion
- at credentials
-def test_rio_clip(runner, tmpdir):
-    """S3 is supported by rio-clip"""
-    outputfile = tmpdir.join('clipped.tif')
-    result = runner.invoke(
-        main_group, ['clip', '--bounds', '420000', '2350000', '420060', '2350060',
-                     '-o', str(outputfile), L8TIF])
-    assert result.exit_code == 0
diff --git a/tests/test_band.py b/tests/test_band.py
index 71fb830..1d62f71 100644
--- a/tests/test_band.py
+++ b/tests/test_band.py
@@ -1,4 +1,10 @@
+import logging
+
 import rasterio
+import rasterio.env
+
+
+logging.basicConfig(level=logging.DEBUG)
 
 def test_band():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
@@ -7,4 +13,3 @@ def test_band():
         assert b.bidx == 1
         assert b.dtype in src.dtypes
         assert b.shape == src.shape
-
diff --git a/tests/test_band_masks.py b/tests/test_band_masks.py
index 0624b10..9b8b813 100644
--- a/tests/test_band_masks.py
+++ b/tests/test_band_masks.py
@@ -6,14 +6,12 @@ import pytest
 
 import rasterio
 from rasterio.enums import MaskFlags
-from rasterio.warnings import NodataShadowWarning
+from rasterio.errors import NodataShadowWarning
 
 
 logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 
-
-
 @pytest.fixture(scope='function')
 def tiffs(tmpdir):
     with rasterio.open('tests/data/RGB.byte.tif') as src:
@@ -84,34 +82,34 @@ def test_warning_shadow(tiffs):
     filename = str(tiffs.join('shadowed.tif'))
     with rasterio.open(filename) as src:
         with pytest.warns(NodataShadowWarning):
-            _ = src.read_masks()
+            src.read_masks()
 
 
 def test_masks():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         rm, gm, bm = src.read_masks()
         r, g, b = src.read(masked=False)
-        assert not r[rm==0].any()
-        assert not g[gm==0].any()
-        assert not b[bm==0].any()
+        assert not r[rm == 0].any()
+        assert not g[gm == 0].any()
+        assert not b[bm == 0].any()
 
 
 def test_masked_true():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         r, g, b = src.read(masked=True)
         rm, gm, bm = src.read_masks()
-        assert (r.mask==~rm.astype('bool')).all()
-        assert (g.mask==~gm.astype('bool')).all()
-        assert (b.mask==~bm.astype('bool')).all()
+        assert (r.mask == ~rm.astype('bool')).all()
+        assert (g.mask == ~gm.astype('bool')).all()
+        assert (b.mask == ~bm.astype('bool')).all()
 
 
 def test_masked_none():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         r, g, b = src.read(masked=True)
         rm, gm, bm = src.read_masks()
-        assert (r.mask==~rm.astype('bool')).all()
-        assert (g.mask==~gm.astype('bool')).all()
-        assert (b.mask==~bm.astype('bool')).all()
+        assert (r.mask == ~rm.astype('bool')).all()
+        assert (g.mask == ~gm.astype('bool')).all()
+        assert (b.mask == ~bm.astype('bool')).all()
 
 
 def test_masking_no_nodata(tiffs):
diff --git a/tests/test_blocks.py b/tests/test_blocks.py
index 4ca99be..c9c6504 100644
--- a/tests/test_blocks.py
+++ b/tests/test_blocks.py
@@ -69,7 +69,7 @@ class RasterBlocksTest(unittest.TestCase):
     def test_blocks(self):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
             self.assertEqual(len(s.block_shapes), 3)
-            self.assertEqual(s.block_shapes, [(3, 791), (3, 791), (3, 791)])
+            self.assertEqual(s.block_shapes, ((3, 791), (3, 791), (3, 791)))
             windows = s.block_windows(1)
             (j,i), first = next(windows)
             self.assertEqual((j,i), (0, 0))
@@ -96,7 +96,7 @@ class WindowReadTest(unittest.TestCase):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
             windows = s.block_windows(1)
             ji, first_window = next(windows)
-            first_block = s.read_band(1, window=first_window)
+            first_block = s.read(1, window=first_window)
             self.assertEqual(first_block.dtype, rasterio.ubyte)
             self.assertEqual(
                 first_block.shape, 
@@ -114,7 +114,7 @@ class WindowWriteTest(unittest.TestCase):
                 name, 'w', 
                 driver='GTiff', width=100, height=100, count=1, 
                 dtype=a.dtype) as s:
-            s.write_band(1, a, window=((30, 80), (10, 60)))
+            s.write(a, indexes=1, window=((30, 80), (10, 60)))
         # subprocess.call(["open", name])
         info = subprocess.check_output(["gdalinfo", "-stats", name])
         self.assert_(
diff --git a/tests/test_colorinterp.py b/tests/test_colorinterp.py
index 3c33b15..19b8644 100644
--- a/tests/test_colorinterp.py
+++ b/tests/test_colorinterp.py
@@ -6,30 +6,28 @@ from rasterio.enums import ColorInterp, PhotometricInterp
 
 def test_cmyk_interp(tmpdir):
     """A CMYK TIFF has cyan, magenta, yellow, black bands."""
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            meta = src.meta
-        meta['photometric'] = 'CMYK'
-        meta['count'] = 4
-        tiffname = str(tmpdir.join('foo.tif'))
-        with rasterio.open(tiffname, 'w', **meta) as dst:
-            assert dst.profile['photometric'] == 'cmyk'
-            assert dst.colorinterp(1) == ColorInterp.cyan
-            assert dst.colorinterp(2) == ColorInterp.magenta
-            assert dst.colorinterp(3) == ColorInterp.yellow
-            assert dst.colorinterp(4) == ColorInterp.black
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        meta = src.meta
+    meta['photometric'] = 'CMYK'
+    meta['count'] = 4
+    tiffname = str(tmpdir.join('foo.tif'))
+    with rasterio.open(tiffname, 'w', **meta) as dst:
+        assert dst.profile['photometric'] == 'cmyk'
+        assert dst.colorinterp(1) == ColorInterp.cyan
+        assert dst.colorinterp(2) == ColorInterp.magenta
+        assert dst.colorinterp(3) == ColorInterp.yellow
+        assert dst.colorinterp(4) == ColorInterp.black
 
 
 def test_ycbcr_interp(tmpdir):
     """A YCbCr TIFF has red, green, blue bands."""
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            meta = src.meta
-        meta['photometric'] = 'ycbcr'
-        meta['compress'] = 'jpeg'
-        meta['count'] = 3
-        tiffname = str(tmpdir.join('foo.tif'))
-        with rasterio.open(tiffname, 'w', **meta) as dst:
-            assert dst.colorinterp(1) == ColorInterp.red
-            assert dst.colorinterp(2) == ColorInterp.green
-            assert dst.colorinterp(3) == ColorInterp.blue
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        meta = src.meta
+    meta['photometric'] = 'ycbcr'
+    meta['compress'] = 'jpeg'
+    meta['count'] = 3
+    tiffname = str(tmpdir.join('foo.tif'))
+    with rasterio.open(tiffname, 'w', **meta) as dst:
+        assert dst.colorinterp(1) == ColorInterp.red
+        assert dst.colorinterp(2) == ColorInterp.green
+        assert dst.colorinterp(3) == ColorInterp.blue
diff --git a/tests/test_colormap.py b/tests/test_colormap.py
index 2008fa0..a059dc9 100644
--- a/tests/test_colormap.py
+++ b/tests/test_colormap.py
@@ -9,43 +9,28 @@ logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 
 def test_write_colormap_warn(tmpdir, recwarn):
-
-    with rasterio.drivers():
-
-        with rasterio.open('tests/data/shade.tif') as src:
-            profile = src.meta
-
-        tiffname = str(tmpdir.join('foo.tif'))
-
-        with rasterio.open(tiffname, 'w', **profile) as dst:
-            dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
-
-        w = recwarn.pop(UserWarning)
-        assert "The value will be ignored" in str(w.message)
+    with rasterio.open('tests/data/shade.tif') as src:
+        profile = src.meta
+    tiffname = str(tmpdir.join('foo.tif'))
+    with rasterio.open(tiffname, 'w', **profile) as dst:
+        dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
+    w = recwarn.pop(UserWarning)
+    assert "The value will be ignored" in str(w.message)
 
 
 def test_write_colormap(tmpdir):
-
-    with rasterio.drivers():
-
-        with rasterio.open('tests/data/shade.tif') as src:
-            shade = src.read_band(1)
-            meta = src.meta
-
-        tiffname = str(tmpdir.join('foo.png'))
-        meta['driver'] = 'PNG'
-
-        with rasterio.open(tiffname, 'w', **meta) as dst:
-            dst.write_band(1, shade)
-            dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
-            cmap = dst.colormap(1)
-            assert cmap[0] == (255, 0, 0, 255)
-            assert cmap[255] == (0, 0, 0, 0)
-
-        with rasterio.open(tiffname) as src:
-            cmap = src.colormap(1)
-            assert cmap[0] == (255, 0, 0, 255)
-            assert cmap[255] == (0, 0, 0, 0)
-
-    # subprocess.call(['open', tiffname])
-
+    with rasterio.open('tests/data/shade.tif') as src:
+        shade = src.read(1)
+        meta = src.meta
+    tiffname = str(tmpdir.join('foo.png'))
+    meta['driver'] = 'PNG'
+    with rasterio.open(tiffname, 'w', **meta) as dst:
+        dst.write(shade, indexes=1)
+        dst.write_colormap(1, {0: (255, 0, 0, 255), 255: (0, 0, 0, 0)})
+        cmap = dst.colormap(1)
+        assert cmap[0] == (255, 0, 0, 255)
+        assert cmap[255] == (0, 0, 0, 0)
+    with rasterio.open(tiffname) as src:
+        cmap = src.colormap(1)
+        assert cmap[0] == (255, 0, 0, 255)
+        assert cmap[255] == (0, 0, 0, 0)
diff --git a/tests/test_crs.py b/tests/test_crs.py
index 30548ad..ccead15 100644
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -8,6 +8,7 @@ import rasterio
 from rasterio import crs
 from rasterio.crs import (
     is_geographic_crs, is_projected_crs, is_same_crs, is_valid_crs)
+from rasterio.errors import CRSError
 
 
 logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
@@ -15,30 +16,27 @@ logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 # When possible, Rasterio gives you the CRS in the form of an EPSG code.
 def test_read_epsg(tmpdir):
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            assert src.crs == {'init': 'epsg:32618'}
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        assert src.crs == {'init': 'epsg:32618'}
 
 def test_read_epsg3857(tmpdir):
     tiffname = str(tmpdir.join('lol.tif'))
     subprocess.call([
-        'gdalwarp', '-t_srs', 'EPSG:3857', 
+        'gdalwarp', '-t_srs', 'EPSG:3857',
         'tests/data/RGB.byte.tif', tiffname])
-    with rasterio.drivers():
-        with rasterio.open(tiffname) as src:
-            assert src.crs == {'init': 'epsg:3857'}
+    with rasterio.open(tiffname) as src:
+        assert src.crs == {'init': 'epsg:3857'}
 
 # Ensure that CRS sticks when we write a file.
 def test_write_3857(tmpdir):
     src_path = str(tmpdir.join('lol.tif'))
     subprocess.call([
-        'gdalwarp', '-t_srs', 'EPSG:3857', 
+        'gdalwarp', '-t_srs', 'EPSG:3857',
         'tests/data/RGB.byte.tif', src_path])
     dst_path = str(tmpdir.join('wut.tif'))
-    with rasterio.drivers():
-        with rasterio.open(src_path) as src:
-            with rasterio.open(dst_path, 'w', **src.meta) as dst:
-                assert dst.crs == {'init': 'epsg:3857'}
+    with rasterio.open(src_path) as src:
+        with rasterio.open(dst_path, 'w', **src.meta) as dst:
+            assert dst.crs == {'init': 'epsg:3857'}
     info = subprocess.check_output([
         'gdalinfo', dst_path])
     # WKT string may vary a bit w.r.t GDAL versions
@@ -146,3 +144,12 @@ def test_is_valid_false():
 
 def test_is_valid():
     assert is_valid_crs('EPSG:4326')
+
+
+def test_empty_json():
+    with pytest.raises(CRSError):
+        crs.from_string('{}')
+    with pytest.raises(CRSError):
+        crs.from_string('[]')
+    with pytest.raises(CRSError):
+        crs.from_string('')
diff --git a/tests/test_deprecations.py b/tests/test_deprecations.py
index ff86464..6f87545 100644
--- a/tests/test_deprecations.py
+++ b/tests/test_deprecations.py
@@ -3,10 +3,11 @@
 # on the way to stabilizing the API for 1.0
 import warnings
 
-import pytest
 import numpy
+import pytest
 
 # New modules
+import rasterio
 from rasterio import windows
 
 # Deprecated modules
@@ -72,3 +73,85 @@ def test_window_union(recwarn):
     new = windows.union(data)
     assert len(recwarn) == 0
     assert old == new
+
+
+def test_stats(recwarn):
+    from rasterio.tool import stats as stats_old
+    from rasterio.rio.insp import stats as stats_new
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        warnings.simplefilter('always')
+        old = stats_old((src, 1))
+        assert len(recwarn) == 1
+        assert recwarn.pop(DeprecationWarning)
+        new = stats_new((src, 1))
+        assert len(recwarn) == 0
+        assert numpy.allclose(numpy.array(new), numpy.array(old))
+
+
+# xfail because for unknown reasons, travis fails with matplotlib errors
+ at pytest.mark.xfail
+def test_show(recwarn):
+    from rasterio.tool import show as show_old
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        warnings.simplefilter('always')
+        old = show_old((src, 1))
+        assert len(recwarn) == 1
+        assert recwarn.pop(DeprecationWarning)
+
+        from rasterio.plot import show as show_new
+        new = show_new((src, 1))
+        assert len(recwarn) == 0
+        assert new == old
+
+
+# xfail because for unknown reasons, travis fails with matplotlib errors
+ at pytest.mark.xfail
+def test_show_hist(recwarn):
+    from rasterio.tool import show_hist as show_old
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        warnings.simplefilter('always')
+        old = show_old((src, 1))
+        assert len(recwarn) == 1
+        assert recwarn.pop(DeprecationWarning)
+
+        from rasterio.plot import show_hist as show_new
+        new = show_new((src, 1))
+        assert len(recwarn) == 0
+        assert new == old
+
+
+def test_mask(recwarn, basic_image_file, basic_geometry):
+    from rasterio.mask import mask as mask_new
+    from rasterio.tools.mask import mask as mask_old
+    nodata_val = 0
+    geometries = [basic_geometry]
+    with rasterio.open(basic_image_file, "r") as src:
+        warnings.simplefilter('once')
+        old = mask_old(src, geometries, crop=False,
+                       nodata=nodata_val, invert=True)
+        recwarn.pop(DeprecationWarning)
+        nwarn = len(recwarn)
+        new = mask_new(src, geometries, crop=False,
+                       nodata=nodata_val, invert=True)
+        assert len(recwarn) == nwarn
+        for parts in zip(new, old):
+            assert numpy.allclose(parts[0], parts[1])
+
+
+def test_merge(recwarn, tmpdir):
+    from rasterio.merge import merge as merge_new
+    from rasterio.tools.merge import merge as merge_old
+    inputs = [
+        'tests/data/rgb1.tif',
+        'tests/data/rgb2.tif',
+        'tests/data/rgb3.tif',
+        'tests/data/rgb4.tif']
+    in_sources = [rasterio.open(x) for x in inputs]
+    warnings.simplefilter('always')
+    old = merge_old(in_sources)
+    recwarn.pop(DeprecationWarning)
+    nwarn = len(recwarn)
+    new = merge_new(in_sources)
+    assert len(recwarn) == nwarn
+    for parts in zip(new, old):
+        assert numpy.allclose(parts[0], parts[1])
diff --git a/tests/test_driver_management.py b/tests/test_driver_management.py
index e31c007..6eac978 100644
--- a/tests/test_driver_management.py
+++ b/tests/test_driver_management.py
@@ -2,45 +2,52 @@ import logging
 import sys
 
 import rasterio
-from rasterio._drivers import driver_count, GDALEnv
+from rasterio._drivers import driver_count
+from rasterio.env import Env
 
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 def test_drivers():
-    with rasterio.drivers() as m:
+    with Env() as m:
         assert driver_count() > 0
-        assert type(m) == GDALEnv
-        
-        n = rasterio.drivers()
+        assert type(m) == Env
+    assert driver_count() > 0
+
+
+def test_drivers_bwd_compat():
+    with rasterio.drivers() as m:
         assert driver_count() > 0
-        assert type(n) == GDALEnv
-
-def test_options(tmpdir):
-    """Test that setting CPL_DEBUG=True results in GDAL debug messages.
-    """
-    logger = logging.getLogger('GDAL')
-    logger.setLevel(logging.DEBUG)
-    logfile1 = str(tmpdir.join('test_options1.log'))
-    fh = logging.FileHandler(logfile1)
-    logger.addHandler(fh)
-    
-    # With CPL_DEBUG=True, expect debug messages from GDAL in
-    # logfile1
-    with rasterio.drivers(CPL_DEBUG=True):
-        with rasterio.open("tests/data/RGB.byte.tif") as src:
+        assert type(m) == Env
+    assert driver_count() > 0
+
+
+def test_cpl_debug_true(tmpdir):
+    """Setting CPL_DEBUG=True results in GDAL debug messages."""
+    log = logging.getLogger('rasterio._gdal')
+    log.setLevel(logging.DEBUG)
+    logfile = str(tmpdir.join('test.log'))
+    fh = logging.FileHandler(logfile)
+    log.addHandler(fh)
+
+    with Env(CPL_DEBUG=True):
+        with rasterio.open("tests/data/RGB.byte.tif"):
+            pass
+
+    log = open(logfile).read()
+    assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" in log
+
+
+def test_cpl_debug_false(tmpdir):
+    """Setting CPL_DEBUG=False results in no GDAL debug messages."""
+    log = logging.getLogger('rasterio._gdal')
+    log.setLevel(logging.DEBUG)
+    logfile = str(tmpdir.join('test.log'))
+    fh = logging.FileHandler(logfile)
+    log.addHandler(fh)
+
+    with Env(CPL_DEBUG=False):
+        with rasterio.open("tests/data/RGB.byte.tif"):
             pass
 
-    log = open(logfile1).read()
-    assert "Option CPL_DEBUG=True" in log
-    
-    # The GDAL env above having exited, CPL_DEBUG should be OFF.
-    logfile2 = str(tmpdir.join('test_options2.log'))
-    fh = logging.FileHandler(logfile2)
-    logger.addHandler(fh)
-
-    with rasterio.open("tests/data/RGB.byte.tif") as src:
-        pass
-    
     # Expect no debug messages from GDAL.
-    log = open(logfile2).read()
+    log = open(logfile).read()
     assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" not in log
diff --git a/tests/test_env.py b/tests/test_env.py
new file mode 100644
index 0000000..428ab1f
--- /dev/null
+++ b/tests/test_env.py
@@ -0,0 +1,190 @@
+# Tests requiring S3 credentials.
+# Collected here to make them easier to skip/xfail.
+
+import logging
+import sys
+
+import boto3
+from packaging.version import parse
+import pytest
+
+import rasterio
+from rasterio._drivers import (
+    GDALEnv, del_gdal_config, get_gdal_config, set_gdal_config, driver_count)
+from rasterio.env import defenv, delenv, getenv, setenv, Env
+from rasterio.errors import EnvError
+from rasterio.rio.main import main_group
+
+
+# Custom markers.
+mingdalversion = pytest.mark.skipif(
+    parse(rasterio.__gdal_version__) < parse('2.1.0dev'),
+    reason="S3 raster access requires GDAL 2.1")
+
+credentials = pytest.mark.skipif(
+    not(boto3.Session()._session.get_credentials()),
+    reason="S3 raster access requires credentials")
+
+
+logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
+
+L8TIF = "s3://landsat-pds/L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF"
+httpstif = "https://landsat-pds.s3.amazonaws.com/L8/139/045/LC81390452014295LGN00/LC81390452014295LGN00_B1.TIF"
+
+
+def test_gdal_config_accessers():
+    """Low level GDAL config access."""
+    assert get_gdal_config('foo') is None
+    set_gdal_config('foo', 'bar')
+    assert get_gdal_config('foo') == 'bar'
+    del_gdal_config('foo')
+    assert get_gdal_config('foo') is None
+
+
+# The 'gdalenv' fixture ensures that gdal configuration is deleted
+# at the end of the test, making tests as isolates as GDAL allows.
+
+def test_env_accessors(gdalenv):
+    """High level GDAL env access"""
+    defenv()
+    setenv(foo='1', bar='2')
+    assert getenv() == rasterio.env._env.options == {'foo': '1', 'bar': '2'}
+    assert get_gdal_config('foo') == '1'
+    assert get_gdal_config('bar') == '2'
+    delenv()
+    assert getenv() == rasterio.env._env.options == {}
+    assert get_gdal_config('foo') is None
+    assert get_gdal_config('bar') is None
+    rasterio.env._env = None
+    with pytest.raises(EnvError):
+        delenv()
+    with pytest.raises(EnvError):
+        setenv()
+    with pytest.raises(EnvError):
+        getenv()
+
+
+def test_no_aws_gdal_config(gdalenv):
+    """Trying to set AWS-specific GDAL config options fails."""
+    with pytest.raises(EnvError):
+        Env(AWS_ACCESS_KEY_ID='x')
+    with pytest.raises(EnvError):
+        Env(AWS_SECRET_ACCESS_KEY='y')
+
+
+def test_env_options(gdalenv):
+    """Test env options."""
+    env = Env(foo='x')
+    assert env.options == {'foo': 'x'}
+    assert not env.previous_options
+    assert getenv() == rasterio.env._env.options == {}
+    with env:
+        assert getenv() == rasterio.env._env.options == {'foo': 'x'}
+    assert getenv() == rasterio.env._env.options == {}
+
+
+def test_aws_session(gdalenv):
+    """Create an Env with a boto3 session."""
+    aws_session = boto3.Session(
+        aws_access_key_id='id', aws_secret_access_key='key',
+        aws_session_token='token', region_name='null-island-1')
+    s = rasterio.env.Env(aws_session=aws_session)
+    assert s._creds.access_key == 'id'
+    assert s._creds.secret_key == 'key'
+    assert s._creds.token == 'token'
+    assert s.aws_session.region_name == 'null-island-1'
+
+
+def test_aws_session_credentials(gdalenv):
+    """Create an Env with a boto3 session."""
+    aws_session = boto3.Session(
+        aws_access_key_id='id', aws_secret_access_key='key',
+        aws_session_token='token', region_name='null-island-1')
+    s = rasterio.env.Env(aws_session=aws_session)
+    assert getenv() == rasterio.env._env.options == {}
+    s.get_aws_credentials()
+    assert getenv() == rasterio.env._env.options == {
+        'aws_access_key_id': 'id', 'aws_region': 'null-island-1',
+        'aws_secret_access_key': 'key', 'aws_session_token': 'token'}
+
+
+def test_with_aws_session_credentials(gdalenv):
+    """Create an Env with a boto3 session."""
+    with Env(aws_access_key_id='id', aws_secret_access_key='key',
+             aws_session_token='token', region_name='null-island-1') as s:
+        assert getenv() == rasterio.env._env.options == {}
+        s.get_aws_credentials()
+        assert getenv() == rasterio.env._env.options == {
+            'aws_access_key_id': 'id', 'aws_region': 'null-island-1',
+            'aws_secret_access_key': 'key', 'aws_session_token': 'token'}
+
+
+def test_session_env_lazy(monkeypatch, gdalenv):
+    """Create an Env with AWS env vars."""
+    monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'id')
+    monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'key')
+    monkeypatch.setenv('AWS_SESSION_TOKEN', 'token')
+    with Env() as s:
+        s.get_aws_credentials()
+        assert getenv() == rasterio.env._env.options == {
+            'aws_access_key_id': 'id', 'aws_secret_access_key': 'key',
+            'aws_session_token': 'token'}
+    monkeypatch.undo()
+
+
+def test_open_with_default_env(gdalenv):
+    """Read from a dataset with a default env."""
+    with rasterio.open('tests/data/RGB.byte.tif') as dataset:
+        assert dataset.count == 3
+
+
+def test_open_with_env(gdalenv):
+    """Read from a dataset with an explicit env."""
+    with Env():
+        with rasterio.open('tests/data/RGB.byte.tif') as dataset:
+            assert dataset.count == 3
+
+
+ at mingdalversion
+ at credentials
+def test_s3_open_with_session(gdalenv):
+    """Read from S3 demonstrating lazy credentials."""
+    with Env():
+        with rasterio.open(L8TIF) as dataset:
+            assert dataset.count == 1
+
+
+ at mingdalversion
+ at credentials
+def test_s3_open_with_default_session(gdalenv):
+    """Read from S3 using default env."""
+    with rasterio.open(L8TIF) as dataset:
+        assert dataset.count == 1
+
+
+ at mingdalversion
+def test_open_https_vsicurl(gdalenv):
+    """Read from HTTPS URL"""
+    with Env():
+        with rasterio.open(httpstif) as dataset:
+            assert dataset.count == 1
+
+
+# CLI tests.
+
+ at mingdalversion
+ at credentials
+def test_s3_rio_info(runner):
+    """S3 is supported by rio-info"""
+    result = runner.invoke(main_group, ['info', L8TIF])
+    assert result.exit_code == 0
+    assert '"crs": "EPSG:32645"' in result.output
+
+
+ at mingdalversion
+ at credentials
+def test_https_rio_info(runner):
+    """HTTPS is supported by rio-info"""
+    result = runner.invoke(main_group, ['info', httpstif])
+    assert result.exit_code == 0
+    assert '"crs": "EPSG:32645"' in result.output
diff --git a/tests/test_err.py b/tests/test_err.py
index 3c182d6..fa1d3d5 100644
--- a/tests/test_err.py
+++ b/tests/test_err.py
@@ -3,6 +3,7 @@
 import pytest
 
 import rasterio
+from rasterio.env import Env
 from rasterio.errors import RasterioIOError
 
 
@@ -16,11 +17,8 @@ def test_io_error(tmpdir):
 
 
 def test_io_error_env(tmpdir):
-    with rasterio.drivers() as env:
-        drivers_start = env.drivers()
-        with pytest.raises(RasterioIOError):
-            rasterio.open(str(tmpdir.join('foo.tif')))
-    assert env.drivers() == drivers_start
+    with pytest.raises(RasterioIOError):
+        rasterio.open(str(tmpdir.join('foo.tif')))
 
 
 def test_bogus_band_error():
diff --git a/tests/test_features.py b/tests/test_features.py
index bdbdab2..e54ebc6 100644
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -5,6 +5,7 @@ import pytest
 
 from affine import Affine
 import rasterio
+from rasterio.env import Env
 from rasterio.features import bounds, geometry_mask, rasterize, sieve, shapes
 
 
@@ -59,7 +60,7 @@ def test_bounds_existing_bbox(basic_featurecollection):
 
 
 def test_geometry_mask(basic_geometry, basic_image_2x2):
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image_2x2 == 0,
             geometry_mask(
@@ -71,7 +72,7 @@ def test_geometry_mask(basic_geometry, basic_image_2x2):
 
 
 def test_geometry_mask_invert(basic_geometry, basic_image_2x2):
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image_2x2,
             geometry_mask(
@@ -86,7 +87,7 @@ def test_geometry_mask_invert(basic_geometry, basic_image_2x2):
 def test_rasterize(basic_geometry, basic_image_2x2):
     """ Rasterize operation should succeed for both an out_shape and out """
 
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image_2x2,
             rasterize([basic_geometry], out_shape=DEFAULT_SHAPE)
@@ -101,7 +102,7 @@ def test_rasterize_invalid_out_dtype(basic_geometry):
     """ A non-supported data type for out should raise an exception """
 
     out = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.int64)
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize([basic_geometry], out=out)
 
@@ -110,7 +111,7 @@ def test_rasterize_shapes_out_dtype_mismatch(basic_geometry):
     """ Shape values must be able to fit in data type for out """
 
     out = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.uint8)
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize([(basic_geometry, 10000000)], out=out)
 
@@ -118,7 +119,7 @@ def test_rasterize_shapes_out_dtype_mismatch(basic_geometry):
 def test_rasterize_missing_out(basic_geometry):
     """ If both out and out_shape are missing, should raise exception """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize([basic_geometry], out=None, out_shape=None)
 
@@ -126,7 +127,7 @@ def test_rasterize_missing_out(basic_geometry):
 def test_rasterize_missing_shapes():
     """ Shapes are required for this operation """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError) as ex:
             rasterize([], out_shape=DEFAULT_SHAPE)
 
@@ -136,7 +137,7 @@ def test_rasterize_missing_shapes():
 def test_rasterize_invalid_shapes():
     """ Invalid shapes should raise an exception rather than be skipped """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError) as ex:
             rasterize([{'foo': 'bar'}], out_shape=DEFAULT_SHAPE)
 
@@ -149,7 +150,7 @@ def test_rasterize_default_value(basic_geometry, basic_image_2x2):
     default_value = 2
     truth = basic_image_2x2 * default_value
 
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             truth,
             rasterize(
@@ -162,7 +163,7 @@ def test_rasterize_default_value(basic_geometry, basic_image_2x2):
 def test_rasterize_invalid_default_value(basic_geometry):
     """ A default value that requires an int64 should raise an exception """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize(
                 [basic_geometry], out_shape=DEFAULT_SHAPE,
@@ -174,7 +175,7 @@ def test_rasterize_fill_value(basic_geometry, basic_image_2x2):
     """ All pixels not covered by shapes should be given fill value """
 
     default_value = 2
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image_2x2 + 1,
             rasterize(
@@ -187,7 +188,7 @@ def test_rasterize_fill_value(basic_geometry, basic_image_2x2):
 def test_rasterize_invalid_fill_value(basic_geometry):
     """ A fill value that requires an int64 should raise an exception """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize(
                 [basic_geometry], out_shape=DEFAULT_SHAPE, fill=1000000000000,
@@ -198,7 +199,7 @@ def test_rasterize_invalid_fill_value(basic_geometry):
 def test_rasterize_fill_value_dtype_mismatch(basic_geometry):
     """ A fill value that doesn't match dtype should fail """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             rasterize(
                 [basic_geometry], out_shape=DEFAULT_SHAPE, fill=1000000,
@@ -207,7 +208,7 @@ def test_rasterize_fill_value_dtype_mismatch(basic_geometry):
 
 
 def test_rasterize_all_touched(basic_geometry, basic_image):
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image,
             rasterize(
@@ -223,7 +224,7 @@ def test_rasterize_value(basic_geometry, basic_image_2x2):
     """
 
     value = 5
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image_2x2 * value,
             rasterize(
@@ -235,7 +236,7 @@ def test_rasterize_value(basic_geometry, basic_image_2x2):
 def test_rasterize_invalid_value(basic_geometry):
     """ A shape value that requires an int64 should raise an exception """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError) as ex:
             rasterize(
                 [(basic_geometry, 1000000000000)], out_shape=DEFAULT_SHAPE
@@ -247,7 +248,7 @@ def test_rasterize_invalid_value(basic_geometry):
 def test_rasterize_supported_dtype(basic_geometry):
     """ Supported data types should return valid results """
 
-    with rasterio.drivers():
+    with Env():
         supported_types = (
             ('int16', -32768),
             ('int32', -2147483648),
@@ -285,7 +286,7 @@ def test_rasterize_supported_dtype(basic_geometry):
 def test_rasterize_unsupported_dtype(basic_geometry):
     """ Unsupported types should all raise exceptions """
 
-    with rasterio.drivers():
+    with Env():
         unsupported_types = (
             ('int8', -127),
             ('int64', 20439845334323),
@@ -312,7 +313,7 @@ def test_rasterize_unsupported_dtype(basic_geometry):
 def test_rasterize_mismatched_dtype(basic_geometry):
     """ Mismatched values and dtypes should raise exceptions """
 
-    with rasterio.drivers():
+    with Env():
         mismatched_types = (('uint8', 3.2423), ('uint8', -2147483648))
         for dtype, default_value in mismatched_types:
             with pytest.raises(ValueError):
@@ -337,7 +338,7 @@ def test_rasterize_geometries_symmetric():
     transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
     truth = numpy.zeros(DEFAULT_SHAPE, dtype=rasterio.ubyte)
     truth[2:5, 2:5] = 1
-    with rasterio.drivers():
+    with Env():
         s = shapes(truth, transform=transform)
         result = rasterize(s, out_shape=DEFAULT_SHAPE, transform=transform)
         assert numpy.array_equal(result, truth)
@@ -352,7 +353,7 @@ def test_rasterize_internal_driver_manager(basic_geometry):
 def test_shapes(basic_image):
     """ Test creation of shapes from pixel values """
 
-    with rasterio.drivers():
+    with Env():
         results = list(shapes(basic_image))
 
         assert len(results) == 2
@@ -380,7 +381,7 @@ def test_shapes(basic_image):
 def test_shapes_band(pixelated_image, pixelated_image_file):
     """ Shapes from a band should match shapes from an array """
 
-    with rasterio.drivers():
+    with Env():
         truth = list(shapes(pixelated_image))
 
         with rasterio.open(pixelated_image_file) as src:
@@ -397,7 +398,7 @@ def test_shapes_connectivity_rook(diagonal_image):
     background.
     """
 
-    with rasterio.drivers():
+    with Env():
         assert len(list(shapes(diagonal_image, connectivity=4))) == 12
 
 
@@ -407,14 +408,14 @@ def test_shapes_connectivity_queen(diagonal_image):
     background.
     """
 
-    with rasterio.drivers():
+    with Env():
         assert len(list(shapes(diagonal_image, connectivity=8))) == 2
 
 
 def test_shapes_connectivity_invalid(diagonal_image):
     """ Invalid connectivity should raise exception """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             assert next(shapes(diagonal_image, connectivity=12))
 
@@ -425,7 +426,7 @@ def test_shapes_mask(basic_image):
     mask = numpy.ones(basic_image.shape, dtype=rasterio.bool_)
     mask[4:5, 4:5] = False
 
-    with rasterio.drivers():
+    with Env():
         results = list(shapes(basic_image, mask=mask))
 
         assert len(results) == 2
@@ -443,7 +444,7 @@ def test_shapes_mask(basic_image):
 def test_shapes_blank_mask(basic_image):
     """ Mask is blank so results should mask shapes without mask """
 
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             list(shapes(
                 basic_image,
@@ -456,7 +457,7 @@ def test_shapes_blank_mask(basic_image):
 def test_shapes_invalid_mask_shape(basic_image):
     """ A mask that is the wrong shape should fail """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             next(shapes(
                 basic_image,
@@ -470,7 +471,7 @@ def test_shapes_invalid_mask_shape(basic_image):
 def test_shapes_invalid_mask_dtype(basic_image):
     """ A mask that is the wrong dtype should fail """
 
-    with rasterio.drivers():
+    with Env():
         for dtype in ('int8', 'int16', 'int32'):
             with pytest.raises(ValueError):
                 next(shapes(
@@ -490,7 +491,7 @@ def test_shapes_supported_dtypes(basic_image):
         ('float32', 1.434532)
     )
 
-    with rasterio.drivers():
+    with Env():
         for dtype, test_value in supported_types:
             shape, value = next(shapes(basic_image.astype(dtype) * test_value))
             assert numpy.allclose(value, test_value)
@@ -507,7 +508,7 @@ def test_shapes_unsupported_dtypes(basic_image):
         ('float64', -98332.133422114)
     )
 
-    with rasterio.drivers():
+    with Env():
         for dtype, test_value in unsupported_types:
             with pytest.raises(ValueError):
                 next(shapes(basic_image.astype(dtype) * test_value))
@@ -525,7 +526,7 @@ def test_sieve_small(basic_image, pixelated_image):
     image should not change the image.
     """
 
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image,
             sieve(pixelated_image, basic_image.sum())
@@ -537,12 +538,12 @@ def test_sieve_large(basic_image):
     Setting the size larger than size of feature should leave us an empty image.
     """
 
-    with rasterio.drivers():
+    with Env():
         assert not numpy.any(sieve(basic_image, basic_image.sum() + 1))
 
 
 def test_sieve_invalid_size(basic_image):
-    with rasterio.drivers():
+    with Env():
         for invalid_size in (0, 45.1234, basic_image.size + 1):
             with pytest.raises(ValueError):
                 sieve(basic_image, invalid_size)
@@ -573,7 +574,7 @@ def test_sieve_connectivity_invalid(basic_image):
 def test_sieve_out(basic_image):
     """ Output array passed in should match the returned array """
 
-    with rasterio.drivers():
+    with Env():
         output = numpy.zeros_like(basic_image)
         output[1:3, 1:3] = 5
         sieved_image = sieve(basic_image, basic_image.sum(), out=output)
@@ -584,7 +585,7 @@ def test_sieve_out(basic_image):
 def test_sieve_invalid_out(basic_image):
     """ Output with different dtype or shape should fail """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             sieve(
                 basic_image, basic_image.sum(),
@@ -611,7 +612,7 @@ def test_sieve_mask(basic_image):
     mask[4:5, 4:5] = False
     truth = basic_image * numpy.invert(mask)
 
-    with rasterio.drivers():
+    with Env():
         sieved_image = sieve(basic_image, basic_image.sum(), mask=mask)
         assert sieved_image.sum() > 0
 
@@ -630,7 +631,7 @@ def test_sieve_blank_mask(basic_image):
     """ A blank mask should have no effect """
 
     mask = numpy.ones(basic_image.shape, dtype=rasterio.bool_)
-    with rasterio.drivers():
+    with Env():
         assert numpy.array_equal(
             basic_image,
             sieve(basic_image, basic_image.sum(), mask=mask)
@@ -640,7 +641,7 @@ def test_sieve_blank_mask(basic_image):
 def test_sieve_invalid_mask_shape(basic_image):
     """ A mask that is the wrong shape should fail """
 
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(ValueError):
             sieve(
                 basic_image, basic_image.sum(),
@@ -654,7 +655,7 @@ def test_sieve_invalid_mask_shape(basic_image):
 def test_sieve_invalid_mask_dtype(basic_image):
     """ A mask that is the wrong dtype should fail """
 
-    with rasterio.drivers():
+    with Env():
         for dtype in ('int8', 'int16', 'int32'):
             with pytest.raises(ValueError):
                 sieve(
@@ -673,7 +674,7 @@ def test_sieve_supported_dtypes(basic_image):
         ('uint16', 65535)
     )
 
-    with rasterio.drivers():
+    with Env():
         for dtype, test_value in supported_types:
             truth = (basic_image).astype(dtype) * test_value
             sieved_image = sieve(truth, basic_image.sum())
@@ -693,7 +694,7 @@ def test_sieve_unsupported_dtypes(basic_image):
         ('float64', -98332.133422114)
     )
 
-    with rasterio.drivers():
+    with Env():
         for dtype, test_value in unsupported_types:
             with pytest.raises(ValueError):
                 sieve(
@@ -705,7 +706,7 @@ def test_sieve_unsupported_dtypes(basic_image):
 def test_sieve_band(pixelated_image, pixelated_image_file):
     """ Sieving a band from a raster file should match sieve of array """
 
-    with rasterio.drivers():
+    with Env():
         truth = sieve(pixelated_image, 9)
 
         with rasterio.open(pixelated_image_file) as src:
diff --git a/tests/test_indexing.py b/tests/test_indexing.py
index f8d6c90..0a659ae 100644
--- a/tests/test_indexing.py
+++ b/tests/test_indexing.py
@@ -126,66 +126,121 @@ def test_data_window_masked_file():
 
 
 def test_window_union():
-    assert windows.union([
+    assert windows.union(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 5))
-    ]) == ((0, 6), (1, 6))
+    ) == ((0, 6), (1, 6))
 
 
 def test_window_intersection():
-    assert windows.intersection([
+    assert windows.intersection(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 5))
-    ]) == ((2, 4), (3, 5))
+    ) == ((2, 4), (3, 5))
 
-    assert windows.intersection([
-        ((0, 6), (3, 6)),
-        ((6, 10), (1, 5))
-    ]) == ((6, 6), (3, 5))
-
-    assert windows.intersection([
+    assert windows.intersection(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 5)),
         ((3, 6), (0, 6))
-    ]) == ((3, 4), (3, 5))
+    ) == ((3, 4), (3, 5))
 
 
 def test_window_intersection_disjunct():
     with pytest.raises(ValueError):
-        windows.intersection([
+        windows.intersection(
             ((0, 6), (3, 6)),
             ((100, 200), (0, 12)),
-            ((7, 12), (7, 12))
-        ])
+            ((7, 12), (7, 12)))
+
+        # touch, no overlap on edge of open interval
+        assert windows.intersection(
+            ((0, 6), (3, 6)),
+            ((6, 10), (1, 5)))
 
 
 def test_windows_intersect():
-    assert windows.intersect([
+    assert windows.intersect(
         ((0, 6), (3, 6)),
-        ((2, 4), (1, 5))
-    ]) == True
+        ((2, 4), (1, 5))) is True
 
-    assert windows.intersect([
+    assert windows.intersect(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 5)),
-        ((3, 6), (0, 6))
-    ]) == True
+        ((3, 6), (0, 6))) is True
+
+    assert windows.intersect(
+        ((0, 2), (0, 2)),
+        ((1, 4), (1, 4))) is True
+
+
+def test_3x3matrix():
+    """For a 3x3 arrangement of 2x2 windows
+      a | b | c
+      ---------
+      d | e | f
+      ---------
+      g | h | i
+
+      i.e. window e is ((2, 4), (2, 4))
+
+      None of them should intersect or have an intersection
+    """
+    from itertools import product, combinations
+
+    pairs = ((0, 2), (2, 4), (4, 6))
+    arrangement = product(pairs, pairs)
+    for wins in combinations(arrangement, 2):
+        assert not windows.intersect(*wins)
+        with pytest.raises(ValueError):
+            windows.intersection(*wins)
 
 
 def test_windows_intersect_disjunct():
-    assert windows.intersect([
+    assert windows.intersect(
         ((0, 6), (3, 6)),
-        ((10, 20), (0, 6))
-    ]) == False
+        ((10, 20), (0, 6))) is False
 
-    assert windows.intersect([
+    # polygons touch at point
+    assert windows.intersect(
+        ((0, 2), (1, 3)),
+        ((2, 4), (3, 5))) is False
+
+    # polygons touch at point, rev order
+    assert windows.intersect(
+        ((2, 4), (3, 5)),
+        ((0, 2), (1, 3))) is False
+
+    # polygons touch at line
+    assert windows.intersect(
+        ((0, 6), (3, 6)),
+        ((6, 10), (1, 5))) is False
+
+    assert windows.intersect(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 5)),
-        ((5, 6), (0, 6))
-    ]) == False
+        ((5, 6), (0, 6))) is False
 
-    assert windows.intersect([
+    assert windows.intersect(
         ((0, 6), (3, 6)),
         ((2, 4), (1, 3)),
-        ((3, 6), (4, 6))
-    ]) == False
+        ((3, 6), (4, 6))) is False
+
+
+def test_iter_args_winfuncs():
+    wins = [
+        ((0, 6), (3, 6)),
+        ((2, 4), (1, 5))]
+
+    assert windows.intersect(*wins) == windows.intersect(wins)
+    assert windows.intersection(*wins) == windows.intersection(wins)
+    assert windows.union(*wins) == windows.union(wins)
+
+
+def test_iter_args():
+    from rasterio.windows import iter_args
+
+    @iter_args
+    def foo(*args):
+        return len(args)
+
+    assert foo([0, 1, 2]) == foo(0, 1, 2) == foo(range(3)) == 3
diff --git a/tests/test_mask_creation.py b/tests/test_mask_creation.py
index 77c6c60..a8dcec8 100644
--- a/tests/test_mask_creation.py
+++ b/tests/test_mask_creation.py
@@ -6,14 +6,15 @@ See https://github.com/mapbox/rasterio/issues/293 for bug report.
 
 import rasterio
 from rasterio.enums import MaskFlags
+from rasterio.env import Env
 
 
 def test_create_internal_mask(data):
     """Write an internal mask to the fixture's RGB.byte.tif."""
-    with rasterio.drivers(GDAL_TIFF_INTERNAL_MASK=True):
+    with Env(GDAL_TIFF_INTERNAL_MASK=True):
         with rasterio.open(str(data.join('RGB.byte.tif')), 'r+') as dst:
             blue = dst.read(1, masked=False)
-            mask = 255*(blue == 0).astype('uint8')
+            mask = 255 * (blue == 0).astype('uint8')
             dst.write_mask(mask)
 
     # There should be no .msk file
@@ -31,11 +32,10 @@ def test_create_internal_mask(data):
 
 def test_create_sidecar_mask(data):
     """Write a .msk sidecar mask."""
-    with rasterio.drivers():
-        with rasterio.open(str(data.join('RGB.byte.tif')), 'r+') as dst:
-            blue = dst.read(1, masked=False)
-            mask = 255*(blue == 0).astype('uint8')
-            dst.write_mask(mask)
+    with rasterio.open(str(data.join('RGB.byte.tif')), 'r+') as dst:
+        blue = dst.read(1, masked=False)
+        mask = 255 * (blue == 0).astype('uint8')
+        dst.write_mask(mask)
 
     # There should be a .msk file in this case.
     assert data.join('RGB.byte.tif').exists()
diff --git a/tests/test_nodata.py b/tests/test_nodata.py
index 98d02c6..0d16408 100644
--- a/tests/test_nodata.py
+++ b/tests/test_nodata.py
@@ -10,12 +10,11 @@ logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 def test_nodata(tmpdir):
     dst_path = str(tmpdir.join('lol.tif'))
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            with rasterio.open(dst_path, 'w', **src.meta) as dst:
-                assert dst.nodata == 0.0
-                assert dst.meta['nodata'] == 0.0
-                assert dst.nodatavals == [0.0, 0.0, 0.0]
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        with rasterio.open(dst_path, 'w', **src.meta) as dst:
+            assert dst.nodata == 0.0
+            assert dst.meta['nodata'] == 0.0
+            assert dst.nodatavals == (0.0, 0.0, 0.0)
     info = subprocess.check_output([
         'gdalinfo', dst_path])
     pattern = b'Band 1.*?NoData Value=0'
@@ -27,14 +26,13 @@ def test_nodata(tmpdir):
 
 def test_set_nodata(tmpdir):
     dst_path = str(tmpdir.join('lol.tif'))
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            meta = src.meta
-            meta['nodata'] = 42
-            with rasterio.open(dst_path, 'w', **meta) as dst:
-                assert dst.nodata == 42
-                assert dst.meta['nodata'] == 42
-                assert dst.nodatavals == [42, 42, 42]
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        meta = src.meta
+        meta['nodata'] = 42
+        with rasterio.open(dst_path, 'w', **meta) as dst:
+            assert dst.nodata == 42
+            assert dst.meta['nodata'] == 42
+            assert dst.nodatavals == (42, 42, 42)
     info = subprocess.check_output([
         'gdalinfo', dst_path])
     pattern = b'Band 1.*?NoData Value=42'
diff --git a/tests/test_open.py b/tests/test_open.py
new file mode 100644
index 0000000..8c19a3c
--- /dev/null
+++ b/tests/test_open.py
@@ -0,0 +1,22 @@
+import pytest
+import rasterio
+
+
+def test_open_bad_path():
+    with pytest.raises(TypeError):
+        rasterio.open(3.14)
+
+
+def test_open_bad_mode_1():
+    with pytest.raises(TypeError):
+        rasterio.open("tests/data/RGB.byte.tif", mode=3.14)
+
+
+def test_open_bad_mode_2():
+    with pytest.raises(ValueError):
+        rasterio.open("tests/data/RGB.byte.tif", mode="foo")
+
+
+def test_open_bad_driver():
+    with pytest.raises(TypeError):
+        rasterio.open("tests/data/RGB.byte.tif", mode="r", driver=3.14)
diff --git a/tests/test_overviews.py b/tests/test_overviews.py
index a219f75..0a5e720 100644
--- a/tests/test_overviews.py
+++ b/tests/test_overviews.py
@@ -4,6 +4,7 @@ import logging
 import sys
 
 from click.testing import CliRunner
+import pytest
 
 import rasterio
 from rasterio.enums import Resampling
@@ -40,7 +41,7 @@ def test_build_overviews_two(data):
         assert src.overviews(3) == [2, 4]
 
 
-def test_build_overviews_three(data):
+def test_build_overviews_average(data):
     inputfile = str(data.join('RGB.byte.tif'))
     with rasterio.open(inputfile, 'r+') as src:
         overview_factors = [2, 4]
@@ -48,3 +49,21 @@ def test_build_overviews_three(data):
         assert src.overviews(1) == [2, 4]
         assert src.overviews(2) == [2, 4]
         assert src.overviews(3) == [2, 4]
+
+
+def test_build_overviews_gauss(data):
+    inputfile = str(data.join('RGB.byte.tif'))
+    with rasterio.open(inputfile, 'r+') as src:
+        overview_factors = [2, 4]
+        src.build_overviews(overview_factors, resampling=Resampling.gauss)
+        assert src.overviews(1) == [2, 4]
+        assert src.overviews(2) == [2, 4]
+        assert src.overviews(3) == [2, 4]
+
+
+def test_test_unsupported_algo(data):
+    inputfile = str(data.join('RGB.byte.tif'))
+    with pytest.raises(ValueError):
+        with rasterio.open(inputfile, 'r+') as src:
+            overview_factors = [2, 4]
+            src.build_overviews(overview_factors, resampling=Resampling.q1)
diff --git a/tests/test_png.py b/tests/test_png.py
index c9324a0..2a31ac5 100644
--- a/tests/test_png.py
+++ b/tests/test_png.py
@@ -15,6 +15,6 @@ def test_write_ubyte(tmpdir):
             name, 'w', 
             driver='PNG', width=100, height=100, count=1, 
             dtype=a.dtype) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
diff --git a/tests/test_read.py b/tests/test_read.py
index 3bbc7d2..7298e02 100644
--- a/tests/test_read.py
+++ b/tests/test_read.py
@@ -22,9 +22,9 @@ class ReaderContextTest(unittest.TestCase):
             self.assertEqual(s.width, 791)
             self.assertEqual(s.height, 718)
             self.assertEqual(s.shape, (718, 791))
-            self.assertEqual(s.dtypes, [rasterio.ubyte]*3)
-            self.assertEqual(s.nodatavals, [0]*3)
-            self.assertEqual(s.indexes, [1,2,3])
+            self.assertEqual(s.dtypes, tuple([rasterio.ubyte] * 3))
+            self.assertEqual(s.nodatavals, (0, 0, 0))
+            self.assertEqual(s.indexes, (1, 2, 3))
             self.assertEqual(s.crs['init'], 'epsg:32618')
             self.assert_(s.crs_wkt.startswith('PROJCS'), s.crs_wkt)
             for i, v in enumerate((101985.0, 2611485.0, 339315.0, 2826915.0)):
@@ -44,8 +44,8 @@ class ReaderContextTest(unittest.TestCase):
         self.assertEqual(s.width, 791)
         self.assertEqual(s.height, 718)
         self.assertEqual(s.shape, (718, 791))
-        self.assertEqual(s.dtypes, [rasterio.ubyte]*3)
-        self.assertEqual(s.nodatavals, [0]*3)
+        self.assertEqual(s.dtypes, tuple([rasterio.ubyte] * 3))
+        self.assertEqual(s.nodatavals, (0, 0, 0))
         self.assertEqual(s.crs['init'], 'epsg:32618')
         self.assertEqual(
             s.affine, 
@@ -67,24 +67,24 @@ class ReaderContextTest(unittest.TestCase):
 
     def test_read_ubyte(self):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
-            a = s.read_band(1)
+            a = s.read(1)
             self.assertEqual(a.dtype, rasterio.ubyte)
 
     def test_read_ubyte_bad_index(self):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
-            self.assertRaises(IndexError, s.read_band, 0)
+            self.assertRaises(IndexError, s.read, 0)
 
     def test_read_ubyte_out(self):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
             a = numpy.zeros((718, 791), dtype=rasterio.ubyte)
-            a = s.read_band(1, a)
+            a = s.read(1, a)
             self.assertEqual(a.dtype, rasterio.ubyte)
 
     def test_read_out_dtype_fail(self):
         with rasterio.open('tests/data/RGB.byte.tif') as s:
             a = numpy.zeros((718, 791), dtype=rasterio.float32)
             try:
-                s.read_band(1, a)
+                s.read(1, a)
             except ValueError as e:
                 assert "the array's dtype 'float32' does not match the file's dtype" in str(e)
             except:
diff --git a/tests/test_read_boundless.py b/tests/test_read_boundless.py
index 26e2ed0..9fdb593 100644
--- a/tests/test_read_boundless.py
+++ b/tests/test_read_boundless.py
@@ -2,6 +2,7 @@ import logging
 import sys
 
 import numpy
+import pytest
 
 import rasterio
 
@@ -101,3 +102,17 @@ def test_read_boundless_noshift():
         r2 = src.read(boundless=True,
                       window=((-1, src.shape[0] + 1), (100, 101)))[0, 0, 0:9]
         assert numpy.array_equal(r1, r2)
+
+
+def test_numpy_warning(recwarn):
+    """Ensure no deprecation warnings
+    On numpy 1.11 and previous versions of rasterio you might see:
+        VisibleDeprecationWarning: using a non-integer number
+        instead of an integer will result in an error in the future
+    """
+    import warnings
+    warnings.simplefilter('always')
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        window = ((-10, 100), (-10, 100))
+        src.read(1, window=window, boundless=True)
+    assert len(recwarn) == 0
diff --git a/tests/test_read_resample.py b/tests/test_read_resample.py
index 5593cf7..3198421 100644
--- a/tests/test_read_resample.py
+++ b/tests/test_read_resample.py
@@ -32,3 +32,15 @@ def test_read_out_shape_resample_up():
         data = s.read(1, out=out, masked=True)
         assert data.shape == (7180, 7910)
         assert data.mean() == s.read(1, masked=True).mean()
+
+
+def test_read_downsample_alpha():
+    with rasterio.Env(GTIFF_IMPLICIT_JPEG_OVR=False):
+        with rasterio.open('tests/data/alpha.tif') as src:
+            out = numpy.zeros((100, 100), dtype=rasterio.ubyte)
+            assert src.width == 1223
+            assert src.height == 1223
+            assert src.count == 4
+            assert src.read(1, out=out, masked=False).shape == out.shape
+            # attempt decimated read of alpha band
+            src.read(4, out=out, masked=False)
diff --git a/tests/test_revolvingdoor.py b/tests/test_revolvingdoor.py
index 40d0e95..57fd186 100644
--- a/tests/test_revolvingdoor.py
+++ b/tests/test_revolvingdoor.py
@@ -24,13 +24,13 @@ class RevolvingDoorTest(unittest.TestCase):
     def test_write_colormap_revolving_door(self):
 
         with rasterio.open('tests/data/shade.tif') as src:
-            shade = src.read_band(1)
+            shade = src.read(1)
             meta = src.meta
 
         tiffname = os.path.join(self.tempdir, 'foo.tif')
         
         with rasterio.open(tiffname, 'w', **meta) as dst:
-            dst.write_band(1, shade)
+            dst.write(shade, indexes=1)
 
         with rasterio.open(tiffname) as src:
             pass
diff --git a/tests/test_rio_convert.py b/tests/test_rio_convert.py
index 2f68637..6bce7df 100644
--- a/tests/test_rio_convert.py
+++ b/tests/test_rio_convert.py
@@ -111,7 +111,7 @@ def test_dtype(tmpdir):
         ['tests/data/RGB.byte.tif', outputname, '--dtype', 'uint16'])
     assert result.exit_code == 0
     with rasterio.open(outputname) as src:
-        assert src.dtypes == ['uint16'] * 3
+        assert src.dtypes == tuple(['uint16'] * 3)
 
 
 def test_dtype_rescaling_uint8_full(tmpdir):
@@ -175,3 +175,14 @@ def test_dtype_rescaling_float64(tmpdir):
         for band in src.read():
             assert round(band.min() + 1.0, 6) == 0.0
             assert round(band.max() - 1.0, 6) == 0.0
+
+
+def test_rgb(tmpdir):
+    outputname = str(tmpdir.join('test.tif'))
+    runner = CliRunner()
+    result = runner.invoke(
+        convert,
+        ['tests/data/RGB.byte.tif', outputname, '--rgb'])
+    assert result.exit_code == 0
+    with rasterio.open(outputname) as src:
+        assert src.colorinterp(1) == rasterio.enums.ColorInterp.red
diff --git a/tests/test_rio_features.py b/tests/test_rio_features.py
index 6a3a9eb..69f2200 100644
--- a/tests/test_rio_features.py
+++ b/tests/test_rio_features.py
@@ -180,9 +180,8 @@ def test_mask_crop(runner, tmpdir, basic_feature, pixelated_image):
         "width": image.shape[1],
         "height": image.shape[0],
         "nodata": 255}
-    with rasterio.drivers():
-        with rasterio.open(outfilename, 'w', **kwargs) as out:
-            out.write_band(1, image)
+    with rasterio.open(outfilename, 'w', **kwargs) as out:
+        out.write(image, indexes=1)
 
     output = str(tmpdir.join('test.tif'))
 
@@ -317,7 +316,7 @@ def test_shapes_with_nodata(runner, pixelated_image, pixelated_image_file):
     pixelated_image[0:2, 8:10] = 255
 
     with rasterio.open(pixelated_image_file, 'r+') as out:
-        out.write_band(1, pixelated_image)
+        out.write(pixelated_image, indexes=1)
 
     result = runner.invoke(
         main_group, ['shapes', pixelated_image_file, '--with-nodata'])
@@ -382,7 +381,7 @@ def test_shapes_mask(runner, pixelated_image, pixelated_image_file):
     pixelated_image[8:10, 8:10] = 255
 
     with rasterio.open(pixelated_image_file, 'r+') as out:
-        out.write_band(1, pixelated_image)
+        out.write(pixelated_image, indexes=1)
 
     result = runner.invoke(
         main_group, ['shapes', pixelated_image_file, '--mask'])
@@ -409,7 +408,7 @@ def test_shapes_mask_sampling(runner, pixelated_image, pixelated_image_file):
     pixelated_image[8:10, 8:10] = 255
 
     with rasterio.open(pixelated_image_file, 'r+') as out:
-        out.write_band(1, pixelated_image)
+        out.write(pixelated_image, indexes=1)
 
     result = runner.invoke(
         main_group,
@@ -433,7 +432,7 @@ def test_shapes_band1_as_mask(runner, pixelated_image, pixelated_image_file):
     pixelated_image[2:3, 2:3] = 4
 
     with rasterio.open(pixelated_image_file, 'r+') as out:
-        out.write_band(1, pixelated_image)
+        out.write(pixelated_image, indexes=1)
 
     result = runner.invoke(
         main_group,
@@ -497,6 +496,23 @@ def test_rasterize_resolution(tmpdir, runner, basic_feature):
         assert numpy.all(data)
 
 
+def test_rasterize_multiresolution(tmpdir, runner, basic_feature):
+    output = str(tmpdir.join('test.tif'))
+    result = runner.invoke(
+        rasterize,
+        [output, '--res', 0.15, '--res', 0.15],
+        input=json.dumps(basic_feature)
+    )
+
+    assert result.exit_code == 0
+    assert os.path.exists(output)
+    with rasterio.open(output) as out:
+        assert numpy.allclose(out.bounds, (2, 2, 4.25, 4.25))
+        data = out.read(1, masked=False)
+        assert data.shape == (15, 15)
+        assert numpy.all(data)
+
+
 def test_rasterize_src_crs(tmpdir, runner, basic_feature):
     output = str(tmpdir.join('test.tif'))
     result = runner.invoke(
@@ -623,6 +639,39 @@ def test_rasterize_like_raster_src_crs_mismatch(tmpdir, runner, basic_feature,
     assert 'GeoJSON does not match crs of --like raster' in result.output
 
 
+def test_rasterize_featurecollection(tmpdir, runner, basic_feature,
+                                     pixelated_image_file):
+    output = str(tmpdir.join('test.tif'))
+    collection = {
+        'type': 'FeatureCollection',
+        'features': [basic_feature]}
+    result = runner.invoke(
+        rasterize,
+        [output, '--like', pixelated_image_file],
+        input=json.dumps(collection)
+    )
+    assert result.exit_code == 0
+
+
+def test_rasterize_src_crs_mismatch(tmpdir, runner, basic_feature,
+                                    pixelated_image_file):
+    output = str(tmpdir.join('test.tif'))
+    result = runner.invoke(
+        rasterize,
+        [output, '--like', pixelated_image_file],
+        input=json.dumps(basic_feature)
+    )
+    assert result.exit_code == 0
+
+    result = runner.invoke(
+        rasterize,
+        [output, '--force-overwrite', '--src-crs', 'EPSG:3857'],
+        input=json.dumps(basic_feature)
+    )
+    assert result.exit_code == 2
+    assert 'GeoJSON does not match crs of existing output raster' in result.output
+
+
 def test_rasterize_property_value(tmpdir, runner, basic_feature):
     output = str(tmpdir.join('test.tif'))
     result = runner.invoke(
@@ -661,7 +710,7 @@ def test_rasterize_like_raster_outside_bounds(tmpdir, runner, basic_feature,
     assert 'outside bounds' in result.output
     assert os.path.exists(output)
     with rasterio.open(output) as out:
-        assert not numpy.any(out.read_band(1, masked=False))
+        assert not numpy.any(out.read(1, masked=False))
 
 
 def test_rasterize_invalid_stdin(tmpdir, runner):
diff --git a/tests/test_rio_info.py b/tests/test_rio_info.py
index 14bf64e..2cbbc76 100644
--- a/tests/test_rio_info.py
+++ b/tests/test_rio_info.py
@@ -618,3 +618,10 @@ def test_info_checksums_only():
         ['info', 'tests/data/RGB.byte.tif', '--checksum', '--bidx', '2'])
     assert result.exit_code == 0
     assert result.output.strip() == '29131'
+
+
+def test_bad_interpreter():
+    from rasterio.rio.insp import main
+    with rasterio.open("tests/data/RGB.byte.tif", 'r') as src:
+        with pytest.raises(ValueError):
+            main("Test banner", src, "PHP")
diff --git a/tests/test_rio_merge.py b/tests/test_rio_merge.py
index 9dfc091..ffd9442 100644
--- a/tests/test_rio_merge.py
+++ b/tests/test_rio_merge.py
@@ -28,17 +28,15 @@ def test_data_dir_1(tmpdir):
         "nodata": 1
     }
 
-    with rasterio.drivers():
+    with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8)
+        data[0:6, 0:6] = 255
+        dst.write(data, indexes=1)
 
-        with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
-            data = numpy.ones((10, 10), dtype=rasterio.uint8)
-            data[0:6, 0:6] = 255
-            dst.write_band(1, data)
-
-        with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
-            data = numpy.ones((10, 10), dtype=rasterio.uint8)
-            data[4:8, 4:8] = 254
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8)
+        data[4:8, 4:8] = 254
+        dst.write(data, indexes=1)
 
     return tmpdir
 
@@ -56,17 +54,15 @@ def test_data_dir_2(tmpdir):
         # these files have undefined nodata.
     }
 
-    with rasterio.drivers():
-
-        with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
-            data = numpy.zeros((10, 10), dtype=rasterio.uint8)
-            data[0:6, 0:6] = 255
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('b.tif')), 'w', **kwargs) as dst:
+        data = numpy.zeros((10, 10), dtype=rasterio.uint8)
+        data[0:6, 0:6] = 255
+        dst.write(data, indexes=1)
 
-        with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
-            data = numpy.zeros((10, 10), dtype=rasterio.uint8)
-            data[4:8, 4:8] = 254
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('a.tif')), 'w', **kwargs) as dst:
+        data = numpy.zeros((10, 10), dtype=rasterio.uint8)
+        data[4:8, 4:8] = 254
+        dst.write(data, indexes=1)
 
     return tmpdir
 
@@ -81,7 +77,7 @@ def test_merge_with_nodata(test_data_dir_1):
     assert os.path.exists(outputname)
     with rasterio.open(outputname) as out:
         assert out.count == 1
-        data = out.read_band(1, masked=False)
+        data = out.read(1, masked=False)
         expected = numpy.ones((10, 10), dtype=rasterio.uint8)
         expected[0:6, 0:6] = 255
         expected[4:8, 4:8] = 254
@@ -109,7 +105,7 @@ def test_merge_without_nodata(test_data_dir_2):
     assert os.path.exists(outputname)
     with rasterio.open(outputname) as out:
         assert out.count == 1
-        data = out.read_band(1, masked=False)
+        data = out.read(1, masked=False)
         expected = numpy.zeros((10, 10), dtype=rasterio.uint8)
         expected[0:6, 0:6] = 255
         expected[4:8, 4:8] = 254
@@ -184,15 +180,14 @@ def test_data_dir_overlapping(tmpdir):
         "nodata": 0
     }
 
-    with rasterio.drivers():
-        with rasterio.open(str(tmpdir.join('se.tif')), 'w', **kwargs) as dst:
-            data = numpy.ones((10, 10), dtype=rasterio.uint8)
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('se.tif')), 'w', **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8)
+        dst.write(data, indexes=1)
 
-        kwargs['transform'] = (-113, 0.2, 0, 45, 0, -0.2)
-        with rasterio.open(str(tmpdir.join('nw.tif')), 'w', **kwargs) as dst:
-            data = numpy.ones((10, 10), dtype=rasterio.uint8) * 2
-            dst.write_band(1, data)
+    kwargs['transform'] = (-113, 0.2, 0, 45, 0, -0.2)
+    with rasterio.open(str(tmpdir.join('nw.tif')), 'w', **kwargs) as dst:
+        data = numpy.ones((10, 10), dtype=rasterio.uint8) * 2
+        dst.write(data, indexes=1)
 
     return tmpdir
 
@@ -209,7 +204,7 @@ def test_merge_overlapping(test_data_dir_overlapping):
         assert out.count == 1
         assert out.shape == (15, 15)
         assert out.bounds == (-114, 43, -111, 46)
-        data = out.read_band(1, masked=False)
+        data = out.read(1, masked=False)
         expected = numpy.zeros((15, 15), dtype=rasterio.uint8)
         expected[0:10, 0:10] = 1
         expected[5:, 5:] = 2
@@ -230,16 +225,15 @@ def test_data_dir_float(tmpdir):
         "nodata": 0
     }
 
-    with rasterio.drivers():
-        with rasterio.open(str(tmpdir.join('two.tif')), 'w', **kwargs) as dst:
-            data = numpy.zeros((10, 10), dtype=rasterio.float64)
-            data[0:6, 0:6] = 255
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('two.tif')), 'w', **kwargs) as dst:
+        data = numpy.zeros((10, 10), dtype=rasterio.float64)
+        data[0:6, 0:6] = 255
+        dst.write(data, indexes=1)
 
-        with rasterio.open(str(tmpdir.join('one.tif')), 'w', **kwargs) as dst:
-            data = numpy.zeros((10, 10), dtype=rasterio.float64)
-            data[4:8, 4:8] = 254
-            dst.write_band(1, data)
+    with rasterio.open(str(tmpdir.join('one.tif')), 'w', **kwargs) as dst:
+        data = numpy.zeros((10, 10), dtype=rasterio.float64)
+        data[4:8, 4:8] = 254
+        dst.write(data, indexes=1)
     return tmpdir
 
 
@@ -253,7 +247,7 @@ def test_merge_float(test_data_dir_float):
     assert os.path.exists(outputname)
     with rasterio.open(outputname) as out:
         assert out.count == 1
-        data = out.read_band(1, masked=False)
+        data = out.read(1, masked=False)
         expected = numpy.ones((10, 10), dtype=rasterio.float64) * -1.5
         expected[0:6, 0:6] = 255
         expected[4:8, 4:8] = 254
@@ -268,29 +262,30 @@ def tiffs(tmpdir):
 
     data = numpy.ones((1, 1, 1), 'uint8')
 
-    kwargs = {'count': '1',
-              'driver': 'GTiff',
-              'dtype': 'uint8',
-              'height': 1,
-              'width': 1}
-
-    kwargs['transform'] = Affine( 1, 0, 1,
-                                  0,-1, 1)
+    kwargs = {
+        'count': '1',
+        'driver': 'GTiff',
+        'dtype': 'uint8',
+        'height': 1,
+        'width': 1}
+
+    kwargs['transform'] = Affine(1, 0, 1,
+                                 0, -1, 1)
     with rasterio.open(str(tmpdir.join('a-sw.tif')), 'w', **kwargs) as r:
         r.write(data * 40)
 
-    kwargs['transform'] = Affine( 1, 0, 2,
-                                  0,-1, 2)
+    kwargs['transform'] = Affine(1, 0, 2,
+                                 0, -1, 2)
     with rasterio.open(str(tmpdir.join('b-ct.tif')), 'w', **kwargs) as r:
         r.write(data * 60)
 
-    kwargs['transform'] = Affine( 2, 0, 3,
-                                  0,-2, 4)
+    kwargs['transform'] = Affine(2, 0, 3,
+                                 0, -2, 4)
     with rasterio.open(str(tmpdir.join('c-ne.tif')), 'w', **kwargs) as r:
         r.write(data * 90)
 
-    kwargs['transform'] = Affine( 2, 0, 2,
-                                  0,-2, 4)
+    kwargs['transform'] = Affine(2, 0, 2,
+                                 0, -2, 4)
     with rasterio.open(str(tmpdir.join('d-ne.tif')), 'w', **kwargs) as r:
         r.write(data * 120)
 
@@ -314,8 +309,8 @@ def test_merge_tiny(tiffs):
 
     with rasterio.open(outputname) as src:
         data = src.read()
-        assert (data[0][0:2,1] == 120).all()
-        assert (data[0][0:2,2:4] == 90).all()
+        assert (data[0][0:2, 1] == 120).all()
+        assert (data[0][0:2, 2:4] == 90).all()
         assert data[0][2][1] == 60
         assert data[0][3][0] == 40
 
@@ -337,8 +332,8 @@ def test_merge_tiny_output_opt(tiffs):
 
     with rasterio.open(outputname) as src:
         data = src.read()
-        assert (data[0][0:2,1] == 120).all()
-        assert (data[0][0:2,2:4] == 90).all()
+        assert (data[0][0:2, 1] == 120).all()
+        assert (data[0][0:2, 2:4] == 90).all()
         assert data[0][2][1] == 60
         assert data[0][3][0] == 40
 
@@ -399,3 +394,11 @@ def test_merge_rgb(tmpdir):
 
     with rasterio.open(outputname) as src:
         assert [src.checksum(i) for i in src.indexes] == [25420, 29131, 37860]
+
+
+def test_merge_tiny_intres(tiffs):
+    from rasterio.merge import merge
+    inputs = [str(x) for x in tiffs.listdir()]
+    inputs.sort()
+    sources = [rasterio.open(x) for x in inputs]
+    merge(sources, res=2)
diff --git a/tests/test_rio_overview.py b/tests/test_rio_overview.py
index 718f6d6..60efd73 100644
--- a/tests/test_rio_overview.py
+++ b/tests/test_rio_overview.py
@@ -53,7 +53,8 @@ def test_rebuild_ls(data):
     runner = CliRunner()
     inputfile = str(data.join('RGB.byte.tif'))
 
-    result = runner.invoke(cli,
+    result = runner.invoke(
+        cli,
         ['overview', inputfile, '--build', '2,4,8', '--resampling', 'cubic'])
     assert result.exit_code == 0
 
diff --git a/tests/test_rio_stack.py b/tests/test_rio_stack.py
index 951a4b3..c7a6b19 100644
--- a/tests/test_rio_stack.py
+++ b/tests/test_rio_stack.py
@@ -14,6 +14,7 @@ def test_stack(tmpdir):
     assert result.exit_code == 0
     with rasterio.open(outputname) as out:
         assert out.count == 3
+        assert out.read(1).max() > 0
 
 
 def test_stack_list(tmpdir):
diff --git a/tests/test_rio_warp.py b/tests/test_rio_warp.py
index 93e90cc..94e01fe 100644
--- a/tests/test_rio_warp.py
+++ b/tests/test_rio_warp.py
@@ -1,12 +1,12 @@
 import logging
 import os
-import re
 import sys
 
 import numpy
 import pytest
 
 import rasterio
+import rasterio.crs
 from rasterio.rio import warp
 from rasterio.rio.main import main_group
 
@@ -323,7 +323,7 @@ def test_warp_reproject_like(runner, tmpdir):
     with rasterio.drivers():
         with rasterio.open(likename, 'w', **kwargs) as dst:
             data = numpy.zeros((10, 10), dtype=rasterio.uint8)
-            dst.write_band(1, data)
+            dst.write(data, indexes=1)
 
     srcname = 'tests/data/shade.tif'
     outputname = str(tmpdir.join('test.tif'))
@@ -352,3 +352,70 @@ def test_warp_reproject_nolostdata(runner, tmpdir):
         # 50 column swath on the right edge should have some ones (gdalwarped has 7223)
         assert arr[0, :, -50:].sum() > 7000
         assert output.crs == {'init': 'epsg:3857'}
+
+
+def test_warp_dst_crs_empty_string(runner, tmpdir):
+    """`$ rio warp --dst-crs ''` used to perform a falsey check that would treat
+    `--dst-crs ''` as though `--dst-crs` was not supplied at all.  If the user
+    gives any value we should let `rasterio.crs.from_string()` handle the
+    validation.
+    """
+
+    infile = 'tests/data/RGB.byte.tif'
+    outfile = str(tmpdir.mkdir('empty_warp_dst_crs.tif').join('test.tif'))
+
+    result = runner.invoke(warp.warp, [
+        infile,
+        outfile,
+        '--dst-crs', ''])
+
+    assert result.exit_code != 0
+    assert 'empty or invalid' in result.output
+
+
+def test_warp_badcrs_dimensions(runner, tmpdir):
+    srcname = 'tests/data/shade.tif'
+    outputname = str(tmpdir.join('test.tif'))
+    result = runner.invoke(warp.warp, [srcname, outputname,
+                                       '--dst-crs', '{"init": "epsg:-1"}',
+                                       '--dimensions', '100', '100'])
+    assert result.exit_code == 2
+    assert "Invalid value for dst_crs" in result.output
+
+
+def test_warp_badcrs_src_bounds(runner, tmpdir):
+    srcname = 'tests/data/shade.tif'
+    outputname = str(tmpdir.join('test.tif'))
+    out_bounds = [-11850000, 4810000, -11849000, 4812000]
+    result = runner.invoke(
+        warp.warp, [srcname, outputname,
+                    '--dst-crs', '{"init": "epsg:-1"}',
+                    '--res', 0.001, '--src-bounds'] + out_bounds)
+    assert result.exit_code == 2
+    assert "Invalid value for dst_crs" in result.output
+
+
+ at pytest.mark.xfail
+def test_warp_reproject_check_invert(runner, tmpdir):
+    srcname = 'tests/data/world.rgb.tif'
+    outputname = str(tmpdir.join('test.tif'))
+    result = runner.invoke(warp.warp, [srcname, outputname,
+                                       '--check-invert-proj', 'yes',
+                                       '--dst-crs', 'EPSG:3759'])
+    assert result.exit_code == 0
+    assert os.path.exists(outputname)
+
+    with rasterio.open(outputname) as output:
+        assert output.crs == {'init': 'epsg:3759'}
+        shape1 = output.shape
+
+    output2name = str(tmpdir.join('test2.tif'))
+    result = runner.invoke(warp.warp, [srcname, output2name,
+                                       '--check-invert-proj', 'no',
+                                       '--dst-crs', 'EPSG:3759'])
+    assert result.exit_code == 0
+    assert os.path.exists(output2name)
+
+    with rasterio.open(output2name) as output:
+        assert output.crs == {'init': 'epsg:3759'}
+        assert output.shape != shape1
diff --git a/tests/test_tool.py b/tests/test_tool.py
index 528a60e..da5acdc 100644
--- a/tests/test_tool.py
+++ b/tests/test_tool.py
@@ -10,15 +10,14 @@ from rasterio.plot import show, show_hist
 from rasterio.rio.insp import stats
 
 def test_stats():
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            results = stats((src, 1))
-            assert results[0] == 0
-            assert results[1] == 255
-            assert np.isclose(results[2], 29.9477)
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        results = stats((src, 1))
+        assert results[0] == 0
+        assert results[1] == 255
+        assert np.isclose(results[2], 29.9477)
 
-            results2 = stats(src.read(1))
-            assert np.allclose(np.array(results), np.array(results2))
+        results2 = stats(src.read(1))
+        assert np.allclose(np.array(results), np.array(results2))
 
 
 def test_show_raster():
@@ -26,15 +25,13 @@ def test_show_raster():
     This test only verifies that code up to the point of plotting with
     matplotlib works correctly.  Tests do not exercise matplotlib.
     """
-
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            try:
-                show((src, 1))
-                fig = plt.gcf()
-                plt.close(fig)
-            except ImportError:
-                pass
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        try:
+            show((src, 1))
+            fig = plt.gcf()
+            plt.close(fig)
+        except ImportError:
+            pass
 
 
 def test_show_raster_no_bounds():
@@ -42,15 +39,13 @@ def test_show_raster_no_bounds():
     This test only verifies that code up to the point of plotting with
     matplotlib works correctly.  Tests do not exercise matplotlib.
     """
-
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            try:
-                show((src, 1), with_bounds=False)
-                fig = plt.gcf()
-                plt.close(fig)
-            except ImportError:
-                pass
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        try:
+            show((src, 1), with_bounds=False)
+            fig = plt.gcf()
+            plt.close(fig)
+        except ImportError:
+            pass
 
 
 def test_show_array():
@@ -58,15 +53,13 @@ def test_show_array():
     This test only verifies that code up to the point of plotting with
     matplotlib works correctly.  Tests do not exercise matplotlib.
     """
-
-    with rasterio.drivers():
-        with rasterio.open('tests/data/RGB.byte.tif') as src:
-            try:
-                show(src.read(1))
-                fig = plt.gcf()
-                plt.close(fig)
-            except ImportError:
-                pass
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        try:
+            show(src.read(1))
+            fig = plt.gcf()
+            plt.close(fig)
+        except ImportError:
+            pass
 
 
 def test_show_hist():
@@ -74,7 +67,6 @@ def test_show_hist():
     This test only verifies that code up to the point of plotting with
     matplotlib works correctly.  Tests do not exercise matplotlib.
     """
-
     with rasterio.open('tests/data/RGB.byte.tif') as src:
         try:
             show_hist((src, 1), bins=256)
diff --git a/tests/test_transform.py b/tests/test_transform.py
index 323b6a2..5be2356 100644
--- a/tests/test_transform.py
+++ b/tests/test_transform.py
@@ -33,6 +33,15 @@ def test_from_bounds():
         assert [round(v, 7) for v in tr] == [round(v, 7) for v in src.affine]
 
 
+def test_array_bounds():
+    with rasterio.open('tests/data/RGB.byte.tif') as src:
+        w, s, e, n = src.bounds
+        height = src.height
+        width = src.width
+        tr = transform.from_bounds(w, s, e, n, src.width, src.height)
+    assert (w, s, e, n) == transform.array_bounds(height, width, tr)
+
+
 def test_window_bounds():
     with rasterio.open('tests/data/RGB.byte.tif') as src:
 
diff --git a/tests/test_update.py b/tests/test_update.py
index cc15c21..1e05b36 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -11,24 +11,23 @@ import rasterio
 
 def test_update_tags(data):
     tiffname = str(data.join('RGB.byte.tif'))
-    with rasterio.drivers():
-        with rasterio.open(tiffname, 'r+') as f:
-            f.update_tags(a='1', b='2')
-            f.update_tags(1, c=3)
-            with pytest.raises(IndexError):
-                f.update_tags(4, d=4)
-            assert f.tags() == {'AREA_OR_POINT': 'Area', 'a': '1', 'b': '2'}
-            assert ('c', '3') in f.tags(1).items()
-        info = subprocess.check_output(["gdalinfo", tiffname]).decode('utf-8')
-        assert re.search("Metadata:\W+a=1\W+AREA_OR_POINT=Area\W+b=2", info)
+    with rasterio.open(tiffname, 'r+') as f:
+        f.update_tags(a='1', b='2')
+        f.update_tags(1, c=3)
+        with pytest.raises(IndexError):
+            f.update_tags(4, d=4)
+        assert f.tags() == {'AREA_OR_POINT': 'Area', 'a': '1', 'b': '2'}
+        assert ('c', '3') in f.tags(1).items()
+    info = subprocess.check_output(["gdalinfo", tiffname]).decode('utf-8')
+    assert re.search("Metadata:\W+a=1\W+AREA_OR_POINT=Area\W+b=2", info)
 
 
 def test_update_band(data):
     tiffname = str(data.join('RGB.byte.tif'))
     with rasterio.open(tiffname, 'r+') as f:
-        f.write_band(1, numpy.zeros(f.shape, dtype=f.dtypes[0]))
+        f.write(numpy.zeros(f.shape, dtype=f.dtypes[0]), indexes=1)
     with rasterio.open(tiffname) as f:
-        assert not f.read_band(1).any()
+        assert not f.read(1).any()
 
 
 def test_update_spatial(data):
@@ -58,7 +57,7 @@ def test_update_nodatavals(data):
     with rasterio.open(tiffname, 'r+') as f:
         f.nodata = 255
     with rasterio.open(tiffname) as f:
-        assert f.nodatavals == [255, 255, 255]
+        assert f.nodatavals == (255, 255, 255)
 
 
 def test_update_nodatavals_error(data):
diff --git a/tests/test_vfs.py b/tests/test_vfs.py
index 140a692..0e1de87 100644
--- a/tests/test_vfs.py
+++ b/tests/test_vfs.py
@@ -32,7 +32,7 @@ def test_parse_path_file_scheme():
 def test_parse_path_file():
     """Correctly parse an ordinary filesystem path"""
     assert parse_path('/foo.tif') == (
-        '/foo.tif', None, None)
+        '/foo.tif', None, '')
 
 
 def test_parse_unknown_scheme():
@@ -48,12 +48,6 @@ def test_vsi_path_scheme():
 
 
 def test_vsi_path_file():
-    """Correctly make a ordinary file path from a parsed file:// URL"""
-    assert vsi_path(
-        'foo.tif', None, 'file') == 'foo.tif'
-
-
-def test_vsi_path_file():
     """Correctly make and ordinary file path from a file path"""
     assert vsi_path(
         'foo.tif', None, 'file') == 'foo.tif'
@@ -95,7 +89,7 @@ def test_read_vfs_none():
 def test_update_vfs(tmpdir, mode):
     """VFS datasets can not be created or updated"""
     with pytest.raises(TypeError):
-        _ = rasterio.open(
+        rasterio.open(
             'zip://{0}'.format(tmpdir), mode,
             **default_gtiff_profile(
                 count=1, width=1, height=1))
diff --git a/tests/test_warnings.py b/tests/test_warnings.py
index c2e6b06..d4db37e 100644
--- a/tests/test_warnings.py
+++ b/tests/test_warnings.py
@@ -1,4 +1,4 @@
-from rasterio.warnings import NodataShadowWarning
+from rasterio.errors import NodataShadowWarning
 
 
 def test_nodata_shadow():
diff --git a/tests/test_warp.py b/tests/test_warp.py
index 3c1a4b8..8336d60 100644
--- a/tests/test_warp.py
+++ b/tests/test_warp.py
@@ -6,8 +6,10 @@ from affine import Affine
 import numpy
 
 import rasterio
+from rasterio.enums import Resampling
+from rasterio.env import Env
 from rasterio.warp import (
-    reproject, Resampling, transform_geom, transform, transform_bounds,
+    reproject, transform_geom, transform, transform_bounds,
     calculate_default_transform)
 
 
@@ -28,7 +30,7 @@ class ReprojectParams(object):
         self.src_crs = src_crs
         self.dst_crs = dst_crs
 
-        with rasterio.drivers():
+        with Env():
             dt, dw, dh = calculate_default_transform(
                 src_crs, dst_crs, width, height, left, bottom, right, top)
             self.dst_transform = dt
@@ -64,7 +66,7 @@ def test_transform():
 
 
 def test_transform_bounds():
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             l, b, r, t = src.bounds
             assert numpy.allclose(
@@ -110,7 +112,7 @@ def test_transform_bounds_densify():
 
 def test_transform_bounds_no_change():
     """ Make sure that going from and to the same crs causes no change """
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             l, b, r, t = src.bounds
             assert numpy.allclose(
@@ -134,7 +136,7 @@ def test_calculate_default_transform():
         0.0028535715391804096, 0.0, -78.95864996545055,
         0.0, -0.0028535715391804096, 25.550873767433984)
 
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             wgs84_crs = {'init': 'EPSG:4326'}
             dst_transform, width, height = calculate_default_transform(
@@ -146,7 +148,7 @@ def test_calculate_default_transform():
 
 
 def test_calculate_default_transform_single_resolution():
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             target_resolution = 0.1
             target_transform = Affine(
@@ -164,7 +166,7 @@ def test_calculate_default_transform_single_resolution():
 
 
 def test_calculate_default_transform_multiple_resolutions():
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             target_resolution = (0.2, 0.1)
             target_transform = Affine(
@@ -183,9 +185,9 @@ def test_calculate_default_transform_multiple_resolutions():
 
 
 def test_reproject_ndarray():
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
-            source = src.read_band(1)
+            source = src.read(1)
 
         dst_crs = dict(
             proj='merc',
@@ -213,9 +215,9 @@ def test_reproject_ndarray():
 
 
 def test_reproject_epsg():
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
-            source = src.read_band(1)
+            source = src.read(1)
 
         dst_crs = {'init': 'EPSG:3857'}
         out = numpy.empty(src.shape, dtype=numpy.uint8)
@@ -232,9 +234,9 @@ def test_reproject_epsg():
 
 def test_reproject_out_of_bounds():
     # using EPSG code not appropriate for the transform should return blank image
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
-            source = src.read_band(1)
+            source = src.read(1)
 
         dst_crs = {'init': 'EPSG:32619'}
         out = numpy.empty(src.shape, dtype=numpy.uint8)
@@ -253,7 +255,7 @@ def test_reproject_nodata():
     params = default_reproject_params()
     nodata = 215
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.uint8)
         out = numpy.zeros((params.dst_width, params.dst_height),
                           dtype=source.dtype)
@@ -277,9 +279,8 @@ def test_reproject_nodata():
 
 def test_reproject_nodata_nan():
     params = default_reproject_params()
-    nodata = 215
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.float32)
         out = numpy.zeros((params.dst_width, params.dst_height),
                           dtype=source.dtype)
@@ -298,8 +299,7 @@ def test_reproject_nodata_nan():
 
         assert (out == 1).sum() == 6215
         assert numpy.isnan(out).sum() == (params.dst_width *
-                                         params.dst_height - 6215)
-
+                                          params.dst_height - 6215)
 
 
 def test_reproject_dst_nodata_default():
@@ -310,7 +310,7 @@ def test_reproject_dst_nodata_default():
 
     params = default_reproject_params()
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.uint8)
         out = numpy.zeros((params.dst_width, params.dst_height),
                           dtype=source.dtype)
@@ -334,7 +334,7 @@ def test_reproject_invalid_dst_nodata():
     """ dst_nodata must be in value range of data type """
     params = default_reproject_params()
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.uint8)
         out = source.copy()
 
@@ -355,7 +355,7 @@ def test_reproject_missing_src_nodata():
     """ src_nodata is required if dst_nodata is not None """
     params = default_reproject_params()
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.uint8)
         out = source.copy()
 
@@ -375,7 +375,7 @@ def test_reproject_invalid_src_nodata():
     """ src_nodata must be in range for data type """
     params = default_reproject_params()
 
-    with rasterio.drivers():
+    with Env():
         source = numpy.ones((params.width, params.height), dtype=numpy.uint8)
         out = source.copy()
 
@@ -394,7 +394,7 @@ def test_reproject_invalid_src_nodata():
 
 def test_reproject_multi():
     """Ndarry to ndarray"""
-    with rasterio.drivers():
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
             source = src.read()
         dst_crs = dict(
@@ -536,9 +536,7 @@ def test_transform_geom():
                 (662054.7979028501, 6772962.86384242),
                 (841909.6014891531, 6731793.200435557),
                 (840726.455490463, 6727039.8672589315),
-                (798842.3090855901, 6569056.500655151)),
-            )
-    }
+                (798842.3090855901, 6569056.500655151)),)}
 
     result = transform_geom('EPSG:3373', 'EPSG:4326', geom)
     assert result['type'] == 'Polygon'
@@ -550,27 +548,27 @@ def test_transform_geom():
     assert len(result['coordinates']) == 2
 
     result = transform_geom(
-        'EPSG:3373', 
-        'EPSG:4326', 
-        geom, 
+        'EPSG:3373',
+        'EPSG:4326',
+        geom,
         antimeridian_cutting=True,
         antimeridian_offset=0)
     assert result['type'] == 'MultiPolygon'
     assert len(result['coordinates']) == 2
 
-    result = transform_geom('EPSG:3373', 'EPSG:4326',  geom,  precision=1)
+    result = transform_geom('EPSG:3373', 'EPSG:4326', geom, precision=1)
     assert int(result['coordinates'][0][0][0] * 10) == -1778
 
 
-def test_reproject_invalid_resampling():
-    # An invalid resampling number fails.
-    with rasterio.drivers():
+def test_reproject_unsupported_resampling():
+    """Values not in enums.Resampling are not supported."""
+    with Env():
         with rasterio.open('tests/data/RGB.byte.tif') as src:
-            source = src.read_band(1)
+            source = src.read(1)
 
         dst_crs = {'init': 'EPSG:32619'}
         out = numpy.empty(src.shape, dtype=numpy.uint8)
-        with pytest.raises(ValueError) as exc_info:
+        with pytest.raises(ValueError):
             reproject(
                 source,
                 out,
@@ -579,4 +577,22 @@ def test_reproject_invalid_resampling():
                 dst_transform=DST_TRANSFORM,
                 dst_crs=dst_crs,
                 resampling=99)
-            assert "99 is not a valid Resampling" in exc_info.value.message
+
+
+def test_reproject_unsupported_resampling_guass():
+    """Resampling.gauss is unsupported."""
+    with Env():
+        with rasterio.open('tests/data/RGB.byte.tif') as src:
+            source = src.read(1)
+
+        dst_crs = {'init': 'EPSG:32619'}
+        out = numpy.empty(src.shape, dtype=numpy.uint8)
+        with pytest.raises(ValueError):
+            reproject(
+                source,
+                out,
+                src_transform=src.transform,
+                src_crs=src.crs,
+                dst_transform=DST_TRANSFORM,
+                dst_crs=dst_crs,
+                resampling=Resampling.gauss)
diff --git a/tests/test_warp_transform.py b/tests/test_warp_transform.py
index ea671cc..d8daf84 100644
--- a/tests/test_warp_transform.py
+++ b/tests/test_warp_transform.py
@@ -4,6 +4,7 @@ import pytest
 
 import rasterio
 from rasterio._warp import _calculate_default_transform
+from rasterio.env import Env
 from rasterio.errors import CRSError
 from rasterio.transform import Affine, from_bounds
 from rasterio.warp import transform_bounds
@@ -20,7 +21,7 @@ def test_identity():
         5009377.085697309)
     transform = from_bounds(left, bottom, right, top, width, height)
 
-    with rasterio.drivers():
+    with Env():
         res_transform, res_width, res_height = _calculate_default_transform(
             src_crs, dst_crs, width, height, left, bottom, right, top)
 
@@ -32,7 +33,7 @@ def test_identity():
 
 def test_transform_bounds():
     """CRSError is raised."""
-    with rasterio.drivers():
+    with Env():
         left, bottom, right, top = (
             -11740727.544603072, 4852834.0517692715, -11584184.510675032,
             5009377.085697309)
@@ -43,7 +44,7 @@ def test_transform_bounds():
 
 
 def test_gdal_transform_notnull():
-    with rasterio.drivers():
+    with Env():
         dt, dw, dh = _calculate_default_transform(
             src_crs={'init': 'EPSG:4326'},
             dst_crs={'init': 'EPSG:32610'},
@@ -57,7 +58,7 @@ def test_gdal_transform_notnull():
 
 
 def test_gdal_transform_fail_dst_crs():
-    with rasterio.drivers():
+    with Env():
         dt, dw, dh = _calculate_default_transform(
             {'init': 'EPSG:4326'},
             '+proj=foobar',
@@ -69,7 +70,7 @@ def test_gdal_transform_fail_dst_crs():
             top=70)
 
 def test_gdal_transform_fail_src_crs():
-    with rasterio.drivers():
+    with Env():
         dt, dw, dh = _calculate_default_transform(
             '+proj=foobar',
             {'init': 'EPSG:32610'},
@@ -85,7 +86,7 @@ def test_gdal_transform_fail_src_crs():
     os.environ.get('GDALVERSION', 'a.b.c').startswith('1.9'),
                    reason="GDAL 1.9 doesn't catch this error")
 def test_gdal_transform_fail_src_crs():
-    with rasterio.drivers():
+    with Env():
         with pytest.raises(CRSError):
             dt, dw, dh = _calculate_default_transform(
                 {'init': 'EPSG:4326'},
diff --git a/tests/test_write.py b/tests/test_write.py
index b8a7e20..d0e543e 100644
--- a/tests/test_write.py
+++ b/tests/test_write.py
@@ -13,32 +13,28 @@ logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 
 def test_validate_dtype_None(tmpdir):
+    """Raise TypeError if there is no dtype"""
     name = str(tmpdir.join("lol.tif"))
-    try:
-        ds = rasterio.open(
-            name, 'w', driver='GTiff', width=100, height=100, count=1,
-            # dtype=None
-            )
-    except TypeError:
-        pass
+    with pytest.raises(TypeError):
+        rasterio.open(
+            name, 'w', driver='GTiff', width=100, height=100, count=1)
+
 
 def test_validate_dtype_str(tmpdir):
     name = str(tmpdir.join("lol.tif"))
-    try:
-        ds = rasterio.open(
+    with pytest.raises(TypeError):
+        rasterio.open(
             name, 'w', driver='GTiff', width=100, height=100, count=1,
             dtype='Int16')
-    except TypeError:
-        pass
+
 
 def test_validate_count_None(tmpdir):
     name = str(tmpdir.join("lol.tif"))
-    try:
-        ds = rasterio.open(
-            name, 'w', driver='GTiff', width=100, height=100, #count=None
+    with pytest.raises(TypeError):
+        rasterio.open(
+            name, 'w', driver='GTiff', width=100, height=100,  # count=None
             dtype=rasterio.uint8)
-    except TypeError:
-        pass
+
 
 def test_no_crs(tmpdir):
     # A dataset without crs is okay.
@@ -46,24 +42,24 @@ def test_no_crs(tmpdir):
     with rasterio.open(
             name, 'w', driver='GTiff', width=100, height=100, count=1,
             dtype=rasterio.uint8) as dst:
-        dst.write_band(1, numpy.ones((100, 100), dtype=rasterio.uint8))
+        dst.write(numpy.ones((100, 100), dtype=rasterio.uint8), indexes=1)
 
 def test_context(tmpdir):
     name = str(tmpdir.join("test_context.tif"))
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=1, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=1,
             dtype=rasterio.ubyte) as s:
         assert s.name == name
         assert s.driver == 'GTiff'
-        assert s.closed == False
+        assert not s.closed
         assert s.count == 1
         assert s.width == 100
         assert s.height == 100
         assert s.shape == (100, 100)
-        assert s.indexes == [1]
+        assert s.indexes == (1,)
         assert repr(s) == "<open RasterUpdater name='%s' mode='w'>" % name
-    assert s.closed == True
+    assert s.closed
     assert s.count == 1
     assert s.width == 100
     assert s.height == 100
@@ -73,23 +69,24 @@ def test_context(tmpdir):
     assert "GTiff" in info
     assert "Size is 100, 100" in info
     assert "Band 1 Block=100x81 Type=Byte, ColorInterp=Gray" in info
-    
+
+
 def test_write_ubyte(tmpdir):
     name = str(tmpdir.mkdir("sub").join("test_write_ubyte.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=1, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=1,
             dtype=a.dtype) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
 def test_write_ubyte_multi(tmpdir):
     name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=1, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=1,
             dtype=a.dtype) as s:
         s.write(a, 1)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
@@ -98,18 +95,18 @@ def test_write_ubyte_multi_list(tmpdir):
     name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi_list.tif"))
     a = numpy.array([numpy.ones((100, 100), dtype=rasterio.ubyte) * 127])
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=1, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=1,
             dtype=a.dtype) as s:
         s.write(a, [1])
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
 def test_write_ubyte_multi_3(tmpdir):
     name = str(tmpdir.mkdir("sub").join("test_write_ubyte_multi_list.tif"))
-    arr = numpy.array(3*[numpy.ones((100, 100), dtype=rasterio.ubyte) * 127])
+    arr = numpy.array(3 * [numpy.ones((100, 100), dtype=rasterio.ubyte) * 127])
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=3, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=3,
             dtype=arr.dtype) as s:
         s.write(arr)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
@@ -119,28 +116,28 @@ def test_write_float(tmpdir):
     name = str(tmpdir.join("test_write_float.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.float32) * 42.0
     with rasterio.open(
-            name, 'w', 
+            name, 'w',
             driver='GTiff', width=100, height=100, count=2,
             dtype=rasterio.float32) as s:
-        assert s.dtypes == [rasterio.float32]*2
-        s.write_band(1, a)
-        s.write_band(2, a)
+        assert s.dtypes == (rasterio.float32, rasterio.float32)
+        s.write(a, indexes=1)
+        s.write(a, indexes=2)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "Minimum=42.000, Maximum=42.000, Mean=42.000, StdDev=0.000" in info
-    
+
 def test_write_crs_transform(tmpdir):
     name = str(tmpdir.join("test_write_crs_transform.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     transform = [101985.0, 300.0379266750948, 0.0,
-                       2826915.0, 0.0, -300.041782729805]
+                 2826915.0, 0.0, -300.041782729805]
     with rasterio.open(
-            name, 'w', 
+            name, 'w',
             driver='GTiff', width=100, height=100, count=1,
-            crs={'units': 'm', 'no_defs': True, 'ellps': 'WGS84', 
+            crs={'units': 'm', 'no_defs': True, 'ellps': 'WGS84',
                  'proj': 'utm', 'zone': 18},
             transform=transform,
             dtype=rasterio.ubyte) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     assert s.crs == {'init': 'epsg:32618'}
     info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
     assert 'PROJCS["UTM Zone 18, Northern Hemisphere",' in info
@@ -152,15 +149,15 @@ def test_write_crs_transform_affine(tmpdir):
     name = str(tmpdir.join("test_write_crs_transform.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     transform = [101985.0, 300.0379266750948, 0.0,
-                       2826915.0, 0.0, -300.041782729805]
+                 2826915.0, 0.0, -300.041782729805]
     with rasterio.open(
-            name, 'w', 
+            name, 'w',
             driver='GTiff', width=100, height=100, count=1,
-            crs={'units': 'm', 'no_defs': True, 'ellps': 'WGS84', 
+            crs={'units': 'm', 'no_defs': True, 'ellps': 'WGS84',
                  'proj': 'utm', 'zone': 18},
             affine=transform,
             dtype=rasterio.ubyte) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     assert s.crs == {'init': 'epsg:32618'}
     info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
     assert 'PROJCS["UTM Zone 18, Northern Hemisphere",' in info
@@ -173,14 +170,14 @@ def test_write_crs_transform_2(tmpdir):
     name = str(tmpdir.join("test_write_crs_transform.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     transform = [101985.0, 300.0379266750948, 0.0,
-                       2826915.0, 0.0, -300.041782729805]
+                 2826915.0, 0.0, -300.041782729805]
     with rasterio.open(
-            name, 'w', 
+            name, 'w',
             driver='GTiff', width=100, height=100, count=1,
             crs='EPSG:32618',
             transform=transform,
             dtype=rasterio.ubyte) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     assert s.crs == {'init': 'epsg:32618'}
     info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
     assert 'PROJCS["WGS 84 / UTM zone 18N",' in info
@@ -193,15 +190,15 @@ def test_write_crs_transform_3(tmpdir):
     name = str(tmpdir.join("test_write_crs_transform.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     transform = [101985.0, 300.0379266750948, 0.0,
-                       2826915.0, 0.0, -300.041782729805]
+                 2826915.0, 0.0, -300.041782729805]
     crs_wkt = 'PROJCS["UTM Zone 18, Northern Hemisphere",GEOGCS["WGS 84",DATUM["unknown",SPHEROID["WGS84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-75],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["Meter",1]]'
     with rasterio.open(
-            name, 'w', 
+            name, 'w',
             driver='GTiff', width=100, height=100, count=1,
             crs=crs_wkt,
             transform=transform,
             dtype=rasterio.ubyte) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     assert s.crs == {'init': 'epsg:32618'}
     info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
     assert 'PROJCS["UTM Zone 18, Northern Hemisphere",' in info
@@ -214,19 +211,19 @@ def test_write_meta(tmpdir):
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     meta = dict(driver='GTiff', width=100, height=100, count=1)
     with rasterio.open(name, 'w', dtype=a.dtype, **meta) as s:
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "Minimum=127.000, Maximum=127.000, Mean=127.000, StdDev=0.000" in info
-    
+
 def test_write_nodata(tmpdir):
     name = str(tmpdir.join("test_write_nodata.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', width=100, height=100, count=2, 
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=2,
             dtype=a.dtype, nodata=0) as s:
-        s.write_band(1, a)
-        s.write_band(2, a)
+        s.write(a, indexes=1)
+        s.write(a, indexes=2)
     info = subprocess.check_output(["gdalinfo", "-stats", name]).decode('utf-8')
     assert "NoData Value=0" in info
 
@@ -235,24 +232,23 @@ def test_guard_nodata(tmpdir):
     name = str(tmpdir.join("test_guard_nodata.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     with pytest.raises(ValueError):
-        with rasterio.open(
-                name, 'w', 
-                driver='GTiff', width=100, height=100, count=2, 
-                dtype=a.dtype, nodata=-1) as s:
-            pass
+        rasterio.open(
+            name, 'w',
+            driver='GTiff', width=100, height=100, count=2,
+            dtype=a.dtype, nodata=-1)
 
 
 def test_write_lzw(tmpdir):
     name = str(tmpdir.join("test_write_lzw.tif"))
     a = numpy.ones((100, 100), dtype=rasterio.ubyte) * 127
     with rasterio.open(
-            name, 'w', 
-            driver='GTiff', 
-            width=100, height=100, count=1, 
+            name, 'w',
+            driver='GTiff',
+            width=100, height=100, count=1,
             dtype=a.dtype,
             compress='LZW') as s:
         assert ('compress', 'LZW') in s.kwds.items()
-        s.write_band(1, a)
+        s.write(a, indexes=1)
     info = subprocess.check_output(["gdalinfo", name]).decode('utf-8')
     assert "LZW" in info
 
@@ -261,12 +257,11 @@ def test_write_noncontiguous(tmpdir):
     ROWS = 4
     COLS = 10
     BANDS = 6
-    with rasterio.drivers():
-        # Create a 3-D random int array (rows, columns, bands)
-        total = ROWS * COLS * BANDS
-        arr = numpy.random.randint(
-            0, 10, size=total).reshape(
-                (ROWS, COLS, BANDS), order='F').astype(numpy.int32)
+    # Create a 3-D random int array (rows, columns, bands)
+    total = ROWS * COLS * BANDS
+    arr = numpy.random.randint(
+        0, 10, size=total).reshape(
+            (ROWS, COLS, BANDS), order='F').astype(numpy.int32)
     kwargs = {
         'driver': 'GTiff',
         'width': COLS,
@@ -276,5 +271,4 @@ def test_write_noncontiguous(tmpdir):
     }
     with rasterio.open(name, 'w', **kwargs) as dst:
         for i in range(BANDS):
-            dst.write_band(i+1, arr[:,:,i])
-
+            dst.write(arr[:, :, i], indexes=i + 1)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/rasterio.git



More information about the Pkg-grass-devel mailing list