internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: rasterio/rasterio

Issue:
AWSSession ignores `region_name` when `aws_unsigned=True`
## Expected behavior and actual behavior.

```python
opts = rasterio.session.AWSSession(aws_unsigned=True, 
region_name='us-west-1').get_credential_options()
print(opts)
```

Expect to see `AWS_REGION='us-west-1'` in the credentials options as well as `AWS_NO_SIGN_REQUEST='YES'`, but only see `AWS_NO_SIGN_REQUEST='YES'`.

I believe that `AWS_REGION` is used by GDAL even when request signing is turned off, I guess mapping from`s3://` to `http(s)://` urls uses that parameter. Current work around is to inject `AWS_REGION=` directly into `rasterio.Env(..)`.

In the current code `aws_region` parameter is populated from a constructed `boto3.Session(..)`, this might evaluate to user supplied `region_name`, which is passed into session constructor or might be set internally by `boto3` by reading from config. 

When `aws_unsigned=True` session is not being constructed (rightfully so), but as a result `region_name` parameter is ignored.



Discussion:

Resolving patch:
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -42,7 +42,7 @@ def emit(self, record):
 
 
 __all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.0.22"
+__version__ = "1.0.23"
 __gdal_version__ = gdal_version()
 
 # Rasterio attaches NullHandler to the 'rasterio' logger and its
diff --git a/rasterio/session.py b/rasterio/session.py
--- a/rasterio/session.py
+++ b/rasterio/session.py
@@ -199,8 +199,6 @@ def __init__(
 
         if session:
             self._session = session
-        elif aws_unsigned:
-            self._session = None
         else:
             self._session = boto3.Session(
                 aws_access_key_id=aws_access_key_id,
@@ -258,7 +256,10 @@ def get_credential_options(self):
 
         """
         if self.unsigned:
-            return {'AWS_NO_SIGN_REQUEST': 'YES'}
+            opts = {'AWS_NO_SIGN_REQUEST': 'YES'}
+            if 'aws_region' in self.credentials:
+                opts['AWS_REGION'] = self.credentials['aws_region']
+            return opts
         else:
             return {k.upper(): v for k, v in self.credentials.items()}
 


Test patch:
diff --git a/tests/test_session.py b/tests/test_session.py
--- a/tests/test_session.py
+++ b/tests/test_session.py
@@ -42,9 +42,9 @@ def test_aws_session_class_session():
 def test_aws_session_class_unsigned():
     """AWSSession works"""
     pytest.importorskip("boto3")
-    sesh = AWSSession(aws_unsigned=True)
-    assert sesh._session is None
+    sesh = AWSSession(aws_unsigned=True, region_name='us-mountain-1')
     assert sesh.get_credential_options()['AWS_NO_SIGN_REQUEST'] == 'YES'
+    assert sesh.get_credential_options()['AWS_REGION'] == 'us-mountain-1'
 
 
 def test_aws_session_class_profile(tmpdir, monkeypatch):
Repository: rasterio/rasterio

Issue:
CRS are unhashable
CRS objects are not hashable:

```
>>> from rasterio.crs import CRS
>>> c = CRS.from_dict(init="epsg:4326")
>>> d = {c: True}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'CRS'
```

I think it would be very nice for caching, for example using `functools.lru_cache`.


Discussion:

Resolving patch:
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -42,7 +42,7 @@ def emit(self, record):
 
 
 __all__ = ['band', 'open', 'pad', 'Env']
-__version__ = "1.0.23"
+__version__ = "1.0.23dev"
 __gdal_version__ = gdal_version()
 
 # Rasterio attaches NullHandler to the 'rasterio' logger and its
diff --git a/rasterio/crs.py b/rasterio/crs.py
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -101,6 +101,9 @@ def __setstate__(self, state):
     def __copy__(self):
         return pickle.loads(pickle.dumps(self))
 
+    def __hash__(self):
+        return hash(self.to_wkt())
+
     def to_proj4(self):
         """Convert CRS to a PROJ4 string
 


Test patch:
diff --git a/tests/test_crs.py b/tests/test_crs.py
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -451,3 +451,13 @@ def test_linear_units():
 def test_crs_copy():
     """CRS can be copied"""
     assert copy.copy(CRS.from_epsg(3857)).wkt.startswith('PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"')
+
+
+def test_crs_hash():
+    """hashes of equivalent CRS are equal"""
+    assert hash(CRS.from_epsg(3857)) == hash(CRS.from_epsg(3857))
+
+
+def test_crs_hash_unequal():
+    """hashes of non-equivalent CRS are not equal"""
+    assert hash(CRS.from_epsg(3857)) != hash(CRS.from_epsg(4326))
Repository: rasterio/rasterio

Issue:
more explicit NotImplementedError messages in read mode ?
In wanting to set a GeoTIFF's CRS, I encountered [this](https://github.com/mapbox/rasterio/blob/master/rasterio/_base.pyx#L516) NotImplementedError when trying to run the following code:
```
with rasterio.open(filepath) as src:
    src.crs = "EPSG:3857"
```
Though in retrospect it is obvious the above will fail without explicitly specifying the proper  mode , i.e. `'r+'` in this case,  I was momentarily thrown off by the error and assumed something was wrong with my approach. Would a more explicit error message be useful here?



Discussion:
If you open a file in "r" mode and try to set the encoding attribute of the file object, you'll see `AttributeError: readonly attribute`. I think we could do something like that.
@sgillies great! thanks for looking into this

Resolving patch:
diff --git a/rasterio/errors.py b/rasterio/errors.py
--- a/rasterio/errors.py
+++ b/rasterio/errors.py
@@ -102,3 +102,7 @@ class UnsupportedOperation(RasterioError):
 
 class OverviewCreationError(RasterioError):
     """Raised when creation of an overview fails"""
+
+
+class DatasetAttributeError(RasterioError, NotImplementedError):
+    """Raised when dataset attributes are misused"""


Test patch:
diff --git a/tests/test_dataset.py b/tests/test_dataset.py
--- a/tests/test_dataset.py
+++ b/tests/test_dataset.py
@@ -11,7 +11,7 @@
 
 import rasterio
 from rasterio.enums import Compression
-from rasterio.errors import RasterioIOError
+from rasterio.errors import RasterioIOError, DatasetAttributeError
 from rasterio.transform import Affine
 
 
@@ -65,3 +65,16 @@ def test_tiled_dataset_blocksize_guard(tmp_path):
         rasterio.open(
             tmp_file, "w", driver="GTiff", count=1, height=13, width=13, dtype="uint8", crs="epsg:3857",
             transform=Affine.identity(), tiled=True, blockxsize=256, blockysize=256)
+
+def test_dataset_readonly_attributes(path_rgb_byte_tif):
+    """Attempts to set read-only attributes fail with DatasetAttributeError"""
+    with pytest.raises(DatasetAttributeError):
+        with rasterio.open(path_rgb_byte_tif) as dataset:
+            dataset.crs = "foo"
+
+
+def test_dataset_readonly_attributes(path_rgb_byte_tif):
+    """Attempts to set read-only attributes still fail with NotImplementedError"""
+    with pytest.raises(NotImplementedError):
+        with rasterio.open(path_rgb_byte_tif) as dataset:
+            dataset.crs = "foo"
Repository: rasterio/rasterio

Issue:
Proposal: Add option in CRS.from_user_input to check if it has the to_wkt method
https://github.com/mapbox/rasterio/blob/b9f34ee559039239c7c0c97bd911b466701a39cd/rasterio/crs.py#L427-L456

It would be nice for interoperability between the rasterio, fiona, and pyproj CRS classes. Thoughts?


Discussion:
Related to #1714
@snowman2 I don't understand, can you show me what you mean with a bit of code?
I am thinking something along the lines of:

```python
elif hasattr(value, "to_wkt") and callable(value.to_wkt):
    return cls.from_wkt(value.to_wkt(), morph_from_esri_dialect=morph_from_esri_dialect)
```
Probably better to put it closer to the front to prevent the fiona.CRS to be converted to a dict as the WKT is better suited for the transfer of CRS information I think.
If this is something that you would like to add, I would be happy to add it in. Just let me know.
I get it now! Sure, sounds good to me.

Resolving patch:
diff --git a/rasterio/crs.py b/rasterio/crs.py
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -427,6 +427,11 @@ def from_user_input(cls, value, morph_from_esri_dialect=False):
         """
         if isinstance(value, cls):
             return value
+        elif hasattr(value, "to_wkt") and callable(value.to_wkt):
+            return cls.from_wkt(
+                value.to_wkt(),
+                morph_from_esri_dialect=morph_from_esri_dialect,
+            )
         elif isinstance(value, int):
             return cls.from_epsg(value)
         elif isinstance(value, dict):


Test patch:
diff --git a/tests/test_crs.py b/tests/test_crs.py
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -40,6 +40,11 @@
     'PARAMETER["Direction",1.0],UNIT["Centimeter",0.01]]]')
 
 
+class CustomCRS(object):
+    def to_wkt(self):
+        return CRS.from_epsg(4326).to_wkt()
+
+
 def test_crs_constructor_dict():
     """Can create a CRS from a dict"""
     crs = CRS({'init': 'epsg:3857'})
@@ -471,3 +476,7 @@ def test_crs_hash_unequal():
 def test_crs84():
     """Create CRS from OGC code"""
     assert "WGS 84" in CRS.from_user_input("urn:ogc:def:crs:OGC::CRS84").wkt
+
+
+def test_from_user_input_custom_crs_class():
+    assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
Repository: rasterio/rasterio

Issue:
Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated,
Just noticed this:
```
rasterio/crs.py:22: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    class CRS(collections.Mapping):
```


Discussion:

Resolving patch:
diff --git a/rasterio/compat.py b/rasterio/compat.py
--- a/rasterio/compat.py
+++ b/rasterio/compat.py
@@ -12,6 +12,7 @@
     import configparser
     from urllib.parse import urlparse
     from collections import UserDict
+    from collections.abc import Mapping
     from inspect import getfullargspec as getargspec
 else:  # pragma: no cover
     string_types = basestring,
@@ -22,3 +23,4 @@
     from urlparse import urlparse
     from UserDict import UserDict
     from inspect import getargspec
+    from collections import Mapping
diff --git a/rasterio/crs.py b/rasterio/crs.py
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -15,11 +15,11 @@
 import pickle
 
 from rasterio._crs import _CRS, all_proj_keys
-from rasterio.compat import string_types
+from rasterio.compat import Mapping, string_types
 from rasterio.errors import CRSError
 
 
-class CRS(collections.Mapping):
+class CRS(Mapping):
     """A geographic or projected coordinate reference system
 
     CRS objects may be created by passing PROJ parameters as keyword
diff --git a/rasterio/rio/clip.py b/rasterio/rio/clip.py
--- a/rasterio/rio/clip.py
+++ b/rasterio/rio/clip.py
@@ -127,10 +127,10 @@ def clip(ctx, files, output, bounds, like, driver, projection,
 
             if 'blockxsize' in out_kwargs and out_kwargs['blockxsize'] > width:
                 del out_kwargs['blockxsize']
-                logger.warn("Blockxsize removed from creation options to accomodate small output width")
+                logger.warning("Blockxsize removed from creation options to accomodate small output width")
             if 'blockysize' in out_kwargs and out_kwargs['blockysize'] > height:
                 del out_kwargs['blockysize']
-                logger.warn("Blockysize removed from creation options to accomodate small output height")
+                logger.warning("Blockysize removed from creation options to accomodate small output height")
 
             with rasterio.open(output, 'w', **out_kwargs) as out:
                 out.write(src.read(window=out_window,


Test patch:
Repository: rasterio/rasterio

Issue:
Proposal: Add option in CRS.from_user_input to check if it has the to_wkt method
https://github.com/mapbox/rasterio/blob/b9f34ee559039239c7c0c97bd911b466701a39cd/rasterio/crs.py#L427-L456

It would be nice for interoperability between the rasterio, fiona, and pyproj CRS classes. Thoughts?


Discussion:
Related to #1714
@snowman2 I don't understand, can you show me what you mean with a bit of code?
I am thinking something along the lines of:

```python
elif hasattr(value, "to_wkt") and callable(value.to_wkt):
    return cls.from_wkt(value.to_wkt(), morph_from_esri_dialect=morph_from_esri_dialect)
```
Probably better to put it closer to the front to prevent the fiona.CRS to be converted to a dict as the WKT is better suited for the transfer of CRS information I think.
If this is something that you would like to add, I would be happy to add it in. Just let me know.
I get it now! Sure, sounds good to me.

Resolving patch:
diff --git a/rasterio/crs.py b/rasterio/crs.py
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -86,7 +86,10 @@ def __bool__(self):
     __nonzero__ = __bool__
 
     def __eq__(self, other):
-        other = CRS.from_user_input(other)
+        try:
+            other = CRS.from_user_input(other)
+        except CRSError:
+            return False
         return (self._crs == other._crs)
 
     def __getstate__(self):


Test patch:
diff --git a/tests/test_crs.py b/tests/test_crs.py
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -478,5 +478,12 @@ def test_crs84():
     assert "WGS 84" in CRS.from_user_input("urn:ogc:def:crs:OGC::CRS84").wkt
 
 
+@pytest.mark.parametrize("other", ["", 4.2, 0])
+def test_equals_different_type(other):
+    """Comparison to non-CRS objects is False"""
+    assert CRS.from_epsg(4326) != other
+
+
 def test_from_user_input_custom_crs_class():
-    assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
+    """Support comparison to foreign objects that provide to_wkt()"""
+    assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
\ No newline at end of file
Repository: rasterio/rasterio

Issue:
optimizing `transform_geom` for repeated transformations
Related to: https://github.com/Toblerity/Fiona/issues/799

Is there interest in adding this to rasterio as well?


Discussion:

Resolving patch:
diff --git a/rasterio/compat.py b/rasterio/compat.py
--- a/rasterio/compat.py
+++ b/rasterio/compat.py
@@ -26,3 +26,8 @@
     from UserDict import UserDict
     from inspect import getargspec
     from collections import Iterable, Mapping
+
+# Users can pass in objects that subclass a few different objects
+# More specifically, rasterio has a CRS() class that subclasses UserDict()
+# In Python 2 UserDict() is in its own module and does not subclass Mapping()
+DICT_TYPES = (dict, Mapping, UserDict)


Test patch:
diff --git a/tests/test_warp.py b/tests/test_warp.py
--- a/tests/test_warp.py
+++ b/tests/test_warp.py
@@ -1057,6 +1057,13 @@ def test_transform_geom_multipolygon(polygon_3373):
     assert all(round(x, 1) == x for x in flatten_coords(result["coordinates"]))
 
 
+def test_transform_geom_array(polygon_3373):
+    geom = [polygon_3373 for _ in range(10)]
+    result = transform_geom("EPSG:3373", "EPSG:4326", geom, precision=1)
+    assert isinstance(result, list)
+    assert len(result) == 10
+
+
 @pytest.mark.parametrize("method", SUPPORTED_RESAMPLING)
 def test_reproject_resampling(path_rgb_byte_tif, method):
     # Expected count of nonzero pixels for each resampling method, based
Repository: rasterio/rasterio

Issue:
BUG: features::bounds() does not support GeometryCollection
As implemented in `_features.pyx` we support `FeatureCollection` and individual geometries, but we don't support `GeometryCollection`.

Fix coming up...

BUG: features::bounds() does not support GeometryCollection
As implemented in `_features.pyx` we support `FeatureCollection` and individual geometries, but we don't support `GeometryCollection`.

Fix coming up...



Discussion:

Resolving patch:
diff --git a/rasterio/features.py b/rasterio/features.py
--- a/rasterio/features.py
+++ b/rasterio/features.py
@@ -373,6 +373,14 @@ def bounds(geometry, north_up=True, transform=None):
         return tuple(geometry['bbox'])
 
     geom = geometry.get('geometry') or geometry
+
+    # geometry must be a geometry, GeometryCollection, or FeatureCollection
+    if not ('coordinates' in geom or 'geometries' in geom or 'features' in geom):
+        raise ValueError(
+            "geometry must be a GeoJSON-like geometry, GeometryCollection, "
+            "or FeatureCollection"
+        )
+
     return _bounds(geom, north_up=north_up, transform=transform)
 
 


Test patch:
diff --git a/tests/test_features.py b/tests/test_features.py
--- a/tests/test_features.py
+++ b/tests/test_features.py
@@ -48,17 +48,42 @@ def test_bounds_z():
     assert bounds(g) == (10, 10, 10, 10)
     assert bounds(MockGeoInterface(g)) == (10, 10, 10, 10)
 
-
-def test_bounds_invalid_obj():
-    with pytest.raises(KeyError):
-        bounds({'type': 'bogus', 'not_coordinates': []})
+@pytest.mark.parametrize('geometry', [
+    {'type': 'Polygon'},
+    {'type': 'Polygon', 'not_coordinates': []},
+    {'type': 'bogus', 'not_coordinates': []},
+    {
+        'type': 'GeometryCollection',
+        'geometries': [
+            {'type': 'Point', 'coordinates': [1, 1]},
+            {'type': 'LineString', 'not_coordinates': [[-10, -20], [10, 20]]},
+        ]
+    }
+])
+def test_bounds_invalid_obj(geometry):
+    with pytest.raises(ValueError, match="geometry must be a GeoJSON-like geometry, GeometryCollection, or FeatureCollection"):
+        bounds(geometry)
 
 
-def test_feature_collection(basic_featurecollection):
+def test_bounds_feature_collection(basic_featurecollection):
     fc = basic_featurecollection
     assert bounds(fc) == bounds(fc['features'][0]) == (2, 2, 4.25, 4.25)
 
 
+def test_bounds_geometry_collection():
+    gc = {
+        'type': 'GeometryCollection',
+        'geometries': [
+            {'type': 'Point', 'coordinates': [1, 1]},
+            {'type': 'LineString', 'coordinates': [[-10, -20], [10, 20]]},
+            {'type': 'Polygon', 'coordinates': [[[5, 5], [25, 50], [25, 5]]]}
+        ]
+    }
+
+    assert bounds(gc) == (-10, -20, 25, 50)
+    assert bounds(MockGeoInterface(gc)) == (-10, -20, 25, 50)
+
+
 def test_bounds_existing_bbox(basic_featurecollection):
     """Test with existing bbox in geojson.
Repository: rasterio/rasterio

Issue:
Can't access requester pays buckets with `rio --aws-requester-pays info <url>` cli tool
## Expected behavior and actual behavior.

Should be able to run something like this if correct AWS credentials are available

```bash
rio --aws-requester-pays \
  info \
 s3://usgs-landsat/collection02/level-2/standard/oli-tirs/2014/168/061/LC08_L2SP_168061_20140102_20200912_02_T1/LC08_L2SP_168061_20140102_20200912_02_T1_SR_B4.TIF

{"blockxsize": 256, "blockysize": 256, "bounds": [172185.0, -276315.0, 399915.0, -44085.0], "colorinterp": ["gray"], "compress": "deflate", "count": 1, "crs": "EPSG:32637", "descriptions": [null], "driver": "GTiff", "dtype": "uint16", "height": 7741, "indexes": [1], "interleave": "band", "lnglat": [37.07703791017367, -1.4485551824205734], "mask_flags": [["nodata"]], "nodata": 0.0, "res": [30.0, 30.0], "shape": [7741, 7591], "tiled": true, "transform": [30.0, 0.0, 172185.0, 0.0, -30.0, -44085.0, 0.0, 0.0, 1.0], "units": [null], "width": 7591}
```

## Steps to reproduce the problem.

1. make sure you have correct credentials, command below should work

```bash
gdalinfo /vsis3/usgs-landsat/collection02/level-2/standard/oli-tirs/2014/168/061/LC08_L2SP_168061_20140102_20200912_02_T1/LC08_L2SP_168061_20140102_20200912_02_T1_SR_B4.TIF \
--config AWS_REQUEST_PAYER requester
```
2. Now access same file with `rio`

```bash
rio --aws-requester-pays \
  info \
 s3://usgs-landsat/collection02/level-2/standard/oli-tirs/2014/168/061/LC08_L2SP_168061_20140102_20200912_02_T1/LC08_L2SP_168061_20140102_20200912_02_T1_SR_B4.TIF
```
and get output similar to

```
Traceback (most recent call last):
  File "rasterio/_base.pyx", line 216, in rasterio._base.DatasetBase.__init__
  File "rasterio/_shim.pyx", line 78, in rasterio._shim.open_dataset
  File "rasterio/_err.pyx", line 213, in rasterio._err.exc_wrap_pointer
rasterio._err.CPLE_AWSAccessDeniedError: Access Denied
```


## Diagnosis

Problem is here:

https://github.com/mapbox/rasterio/blob/2e8ec36a52b6d57e5364f1b415e3573f6e3084a3/rasterio/rio/main.py#L95-L101

basically AWSSession is not being constructed even if aws option was requested.

Should be this instead:

```diff
-    if aws_profile or aws_no_sign_requests:
+    if aws_profile or aws_no_sign_requests or aws_requester_pays:
```

## Operating system

Ubuntu 20.04

## Rasterio version and provenance

1.1.8 compiled against local gdal 3.0.4



Discussion:

Resolving patch:
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -92,7 +92,7 @@ def main_group(
     ctx.obj["verbosity"] = verbosity
     ctx.obj["aws_profile"] = aws_profile
     envopts = {"CPL_DEBUG": (verbosity > 2)}
-    if aws_profile or aws_no_sign_requests:
+    if aws_profile or aws_no_sign_requests or aws_requester_pays:
         ctx.obj["env"] = rasterio.Env(
             session=AWSSession(
                 profile_name=aws_profile,


Test patch:
Repository: rasterio/rasterio

Issue:
WarpedVRT context exit doesn't not set the dataset as closed
It's me again with a WarpedVRT bug (I'm sorry). 

Basically I wanted to know the state of the WarpedVRT dataset after I exited the context manager, and it seems that the WarpedVRT is not set to `closed` but if I try to to `vrt.read()` rasterio will error with `RasterioIOError: Dataset is closed: WarpedVRT(tests/fixtures/cog_gcps.tif)`

```python
with rasterio.open("tests/fixtures/cog.tif") as src:
    with WarpedVRT(src) as vrt:
        assert not src.closed
        assert not vrt.closed   # <open WarpedVRT name='WarpedVRT(tests/fixtures/cog.tif)' mode='r'>
    assert vrt.closed   # <--- AssertionError  | <open WarpedVRT name='WarpedVRT(tests/fixtures/cog.tif)' mode='r'>
assert src.closed
assert vrt.closed. # <-- still not closed here either
```

System: 
- Mac Os 
- rasterio:  '1.2b4'`


Discussion:

Resolving patch:
diff --git a/rasterio/vrt.py b/rasterio/vrt.py
--- a/rasterio/vrt.py
+++ b/rasterio/vrt.py
@@ -122,9 +122,6 @@ def __exit__(self, *args, **kwargs):
     def __del__(self):
         self.close()
 
-    def close(self):
-        self.stop()
-
 
 def _boundless_vrt_doc(
         src_dataset, nodata=None, background=None, hidenodata=False,


Test patch:
diff --git a/tests/test_warpedvrt.py b/tests/test_warpedvrt.py
--- a/tests/test_warpedvrt.py
+++ b/tests/test_warpedvrt.py
@@ -608,3 +608,11 @@ def test_vrt_mem_src_kept_alive(path_rgb_byte_tif):
 
     assert (vrt.read() != 0).any()
     vrt.close()
+
+
+def test_warped_vrt_is_closed(path_rgb_byte_tif):
+    """A VirtualVRT should be set as closed on exit."""
+    with rasterio.open(path_rgb_byte_tif) as src:
+        with WarpedVRT(src, crs=DST_CRS) as vrt:
+            assert not vrt.closed
+        assert vrt.closed