Repository: rasterio/rasterio

Issue:
Add missing "w+" mode
Towards resolving #1309 


Discussion:

Resolving patch:
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -86,51 +86,55 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
     In read ('r') or read/write ('r+') mode, no keyword arguments are
     required: these attributes are supplied by the opened dataset.
 
-    In write ('w') mode, the driver, width, height, count, and dtype
+    In write ('w' or 'w+') mode, the driver, width, height, count, and dtype
     keywords are strictly required.
 
     Parameters
     ----------
     fp : str, file object or pathlib.Path object
-        A filename or URL, or file object opened in binary ('rb') mode
+        A filename or URL, a file object opened in binary ('rb') mode,
+        or a Path object.
     mode : str, optional
-        'r' (read, the default), 'r+' (read/write), or 'w' (write)
+        'r' (read, the default), 'r+' (read/write), 'w' (write), or
+        'w+' (write/read).
     driver : str, optional
         A short format driver name (e.g. "GTiff" or "JPEG") or a list of
         such names (see GDAL docs at
-        http://www.gdal.org/formats_list.html). In 'w' mode a single
-        name is required. In 'r' or 'r+' mode the driver can usually be
-        omitted. Registered drivers will be tried sequentially until a
-        match is found. When multiple drivers are available for a format
-        such as JPEG2000, one of them can be selected by using this
-        keyword argument.
+        http://www.gdal.org/formats_list.html). In 'w' or 'w+' modes
+        a single name is required. In 'r' or 'r+' modes the driver can
+        usually be omitted. Registered drivers will be tried
+        sequentially until a match is found. When multiple drivers are
+        available for a format such as JPEG2000, one of them can be
+        selected by using this keyword argument.
     width, height : int, optional
         The numbers of rows and columns of the raster dataset. Required
-        in 'w' mode, they are ignored in 'r' or 'r+' mode.
+        in 'w' or 'w+' modes, they are ignored in 'r' or 'r+' modes.
     count : int, optional
-        The count of dataset bands. Required in 'w' mode, it is ignored
-        in 'r' or 'r+' mode.
+        The count of dataset bands. Required in 'w' or 'w+' modes, it is
+        ignored in 'r' or 'r+' modes.
     dtype : str or numpy dtype
         The data type for bands. For example: 'uint8' or
-        ``rasterio.uint16``. Required in 'w' mode, it is ignored in
-        'r' or 'r+' mode.
+        ``rasterio.uint16``. Required in 'w' or 'w+' modes, it is
+        ignored in 'r' or 'r+' modes.
     crs : str, dict, or CRS; optional
-        The coordinate reference system. Required in 'w' mode, it is
-        ignored in 'r' or 'r+' mode.
+        The coordinate reference system. Required in 'w' or 'w+' modes,
+        it is ignored in 'r' or 'r+' modes.
     transform : Affine instance, optional
         Affine transformation mapping the pixel space to geographic
-        space. Required in 'w' mode, it is ignored in 'r' or 'r+' mode.
+        space. Required in 'w' or 'w+' modes, it is ignored in 'r' or
+        'r+' modes.
     nodata : int, float, or nan; optional
         Defines the pixel value to be interpreted as not valid data.
-        Required in 'w' mode, it is ignored in 'r' or 'r+' mode.
+        Required in 'w' or 'w+' modes, it is ignored in 'r' or 'r+'
+        modes.
     sharing : bool
         A flag that allows sharing of dataset handles. Default is
         `True`. Should be set to `False` in a multithreaded:w program.
     kwargs : optional
-        These are passed to format drivers as directives for creating
-        or interpreting datasets. For example: in 'w' a `tiled=True`
-        keyword argument will direct the GeoTIFF format driver to
-        create a tiled, rather than striped, TIFF.
+        These are passed to format drivers as directives for creating or
+        interpreting datasets. For example: in 'w' or 'w+' modes
+        a `tiled=True` keyword argument will direct the GeoTIFF format
+        driver to create a tiled, rather than striped, TIFF.
 
     Returns
     -------
@@ -165,8 +169,7 @@ def open(fp, mode='r', driver=None, width=None, height=None, count=None,
     """
 
     if not isinstance(fp, string_types):
-        if not (hasattr(fp, 'read') or hasattr(fp, 'write')
-                or isinstance(fp, Path)):
+        if not (hasattr(fp, 'read') or hasattr(fp, 'write') or isinstance(fp, Path)):
             raise TypeError("invalid path or file: {0!r}".format(fp))
     if mode and not isinstance(mode, string_types):
         raise TypeError("invalid mode: {0!r}".format(mode))
@@ -219,7 +222,7 @@ def fp_reader(fp):
 
         return fp_reader(fp)
 
-    elif mode == 'w' and hasattr(fp, 'write'):
+    elif mode in ('w', 'w+') and hasattr(fp, 'write'):
 
         @contextmanager
         def fp_writer(fp):
@@ -255,12 +258,13 @@ def fp_writer(fp):
             if mode == 'r':
                 s = DatasetReader(fp, driver=driver, **kwargs)
             elif mode == 'r-':
-                warnings.warn("'r-' mode is deprecated, use 'r'",
-                              DeprecationWarning)
+                warnings.warn(
+                    "'r-' mode is deprecated and will be removed in 1.0, use 'r'",
+                    DeprecationWarning)
                 s = DatasetReader(fp)
             elif mode == 'r+':
                 s = get_writer_for_path(fp)(fp, mode, driver=driver, **kwargs)
-            elif mode == 'w':
+            elif mode.startswith("w"):
                 s = get_writer_for_driver(driver)(fp, mode, driver=driver,
                                                   width=width, height=height,
                                                   count=count, crs=crs,


Test patch:
diff --git a/tests/test_dataset_rw.py b/tests/test_dataset_rw.py
new file mode 100644
--- /dev/null
+++ b/tests/test_dataset_rw.py
@@ -0,0 +1,37 @@
+"""Tests of r+ and w+ modes"""
+
+import numpy as np
+import pytest
+
+import rasterio
+from rasterio.errors import RasterioDeprecationWarning
+from rasterio.profiles import DefaultGTiffProfile
+
+
+def test_read_wplus_mode(tmpdir):
+    """A dataset opened in 'w+' mode can be read"""
+    path = tmpdir.join('test.tif')
+    profile = DefaultGTiffProfile(count=1, width=300, height=300)
+
+    with rasterio.open(str(path), "w+", **profile) as dst:
+
+        dst.write(255 * np.ones((1, 300, 300), dtype='uint8'))
+
+        assert (dst.read() == 255).all()
+
+        dst.write(3 * np.ones((1, 300, 300), dtype='uint8'))
+
+        assert (dst.read() == 3).all()
+
+
+def test_read_w_mode_warning(tmpdir):
+    """Get a deprecation warning when reading from dataset opened in "w" mode"""
+    path = tmpdir.join('test.tif')
+    profile = DefaultGTiffProfile(count=1, width=300, height=300)
+
+    with rasterio.open(str(path), "w", **profile) as dst:
+
+        dst.write(255 * np.ones((1, 300, 300), dtype='uint8'))
+
+        with pytest.warns(RasterioDeprecationWarning):
+            assert (dst.read() == 255).all()