Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add allow_unsupported_drivers option to fiona.open #1126

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 16 additions & 15 deletions docs/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1337,30 +1337,31 @@ The single shapefile may also be accessed like so:
Unsupported drivers
-------------------

In :py:attr:`fiona.supported_drivers` a selection of GDAL/OGR's
drivers that is tested to work with Fiona is maintained. By default, Fiona
allows only these drivers with their listed access modes: r for read support,
respectively a for append and w for write.

These restrictions can be circumvented by modifying :py:attr:`fiona.supported_drivers`:
Fiona maintains a list of OGR drivers in :py:attr:`fiona.supported_drivers`
that are tested and known to work together with Fiona. Opening a dataset using
an unsupported driver or access mode results in an :py:attr: `DriverError`
exception. By passing `allow_unsupported_drivers=True` to :py:attr:`fiona.open`
no compatibility checks are performed and unsupported OGR drivers can be used.
However, there are no guarantees that Fiona will be able to access or write
data correctly using an unsupported driver.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rbuffat this is great! You've explained the situation and the intent of the feature very well.


.. code-block:: python
import fiona
import fiona
fiona.drvsupport.supported_drivers["LIBKML"] = "raw"
with fiona.open("file.kmz") as collection:
pass
with fiona.open("file.kmz", allow_unsupported_drivers=True) as collection:
...
It should, however, first be verified, if the local installation of GDAL/OGR
includes the required driver:
Not all OGR drivers are necessarily enabled in every GDAL distribution. The
following code snippet lists the drivers included in the GDAL installation
used by Fiona:

.. code-block:: python
from fiona.env import Env
from fiona.env import Env
with Env() as gdalenv:
print(gdalenv.drivers().keys())
with Env() as gdalenv:
print(gdalenv.drivers().keys())
MemoryFile and ZipMemoryFile
----------------------------
Expand Down
32 changes: 26 additions & 6 deletions fiona/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,11 @@
writing modes) flush contents to disk when their ``with`` blocks end.
"""

from contextlib import contextmanager
import logging
import os
import sys
import warnings
import platform
from collections import OrderedDict

try:
from pathlib import Path
Expand Down Expand Up @@ -128,9 +126,20 @@ class Path:


@ensure_env_with_credentials
def open(fp, mode='r', driver=None, schema=None, crs=None, encoding=None,
layer=None, vfs=None, enabled_drivers=None, crs_wkt=None,
**kwargs):
def open(
fp,
mode="r",
driver=None,
schema=None,
crs=None,
encoding=None,
layer=None,
vfs=None,
enabled_drivers=None,
crs_wkt=None,
allow_unsupported_drivers=False,
**kwargs
):
"""Open a collection for read, append, or write
In write mode, a driver name such as "ESRI Shapefile" or "GPX" (see
Expand Down Expand Up @@ -214,6 +223,8 @@ def open(fp, mode='r', driver=None, schema=None, crs=None, encoding=None,
wkt_version : fiona.enums.WktVersion or str, optional
Version to use to for the CRS WKT.
Defaults to GDAL's default (WKT1_GDAL for GDAL 3).
allow_unsupported_drivers : bool
If set to true do not limit GDAL drivers to set set of known working.
kwargs : mapping
Other driver-specific parameters that will be interpreted by
the OGR library as layer creation or opening options.
Expand All @@ -232,6 +243,7 @@ def open(fp, mode='r', driver=None, schema=None, crs=None, encoding=None,
layer=layer,
encoding=encoding,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
**kwargs
)
colxn._env.enter_context(memfile)
Expand All @@ -246,6 +258,7 @@ def open(fp, mode='r', driver=None, schema=None, crs=None, encoding=None,
layer=layer,
encoding=encoding,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
crs_wkt=crs_wkt,
**kwargs
)
Expand All @@ -269,7 +282,11 @@ def func(*args, **kwds):
# TODO: test for a shared base class or abstract type.
elif isinstance(fp, MemoryFile):
if mode.startswith("r"):
colxn = fp.open(driver=driver, **kwargs)
colxn = fp.open(
driver=driver,
allow_unsupported_drivers=allow_unsupported_drivers,
**kwargs
)

# Note: FilePath does not support writing and an exception will
# result from this.
Expand All @@ -281,6 +298,7 @@ def func(*args, **kwds):
layer=layer,
encoding=encoding,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
crs_wkt=crs_wkt,
**kwargs
)
Expand Down Expand Up @@ -312,6 +330,7 @@ def func(*args, **kwds):
encoding=encoding,
layer=layer,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
**kwargs
)
elif mode == "w":
Expand All @@ -325,6 +344,7 @@ def func(*args, **kwds):
layer=layer,
enabled_drivers=enabled_drivers,
crs_wkt=crs_wkt,
allow_unsupported_drivers=allow_unsupported_drivers,
**kwargs
)
else:
Expand Down
22 changes: 13 additions & 9 deletions fiona/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def __init__(
ignore_geometry=False,
include_fields=None,
wkt_version=None,
allow_unsupported_drivers=False,
**kwargs
):

Expand Down Expand Up @@ -145,6 +146,7 @@ def __init__(
self.include_fields = include_fields
self.ignore_fields = ignore_fields
self.ignore_geometry = bool(ignore_geometry)
self._allow_unsupported_drivers = allow_unsupported_drivers
self._env = None
self._closed = True

Expand Down Expand Up @@ -197,10 +199,11 @@ def __init__(
driver = "ESRI Shapefile"
if not driver:
raise DriverError("no driver")
elif driver not in supported_drivers:
raise DriverError("unsupported driver: %r" % driver)
elif self.mode not in supported_drivers[driver]:
raise DriverError("unsupported mode: %r" % self.mode)
if not allow_unsupported_drivers:
if driver not in supported_drivers:
raise DriverError("unsupported driver: %r" % driver)
if self.mode not in supported_drivers[driver]:
raise DriverError("unsupported mode: %r" % self.mode)
self._driver = driver

if not schema:
Expand Down Expand Up @@ -257,11 +260,12 @@ def __repr__(self):
)

def guard_driver_mode(self):
driver = self.session.get_driver()
if driver not in supported_drivers:
raise DriverError("unsupported driver: %r" % driver)
if self.mode not in supported_drivers[driver]:
raise DriverError("unsupported mode: %r" % self.mode)
if not self._allow_unsupported_drivers:
driver = self.session.get_driver()
if driver not in supported_drivers:
raise DriverError("unsupported driver: %r" % driver)
if self.mode not in supported_drivers[driver]:
raise DriverError("unsupported mode: %r" % self.mode)

@property
def driver(self):
Expand Down
38 changes: 31 additions & 7 deletions fiona/io.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Classes capable of reading and writing collections
"""

from collections import OrderedDict
import logging

from fiona.ogrext import MemoryFileBase
Expand Down Expand Up @@ -31,9 +30,20 @@ def __init__(self, file_or_bytes=None, filename=None, ext=""):
super().__init__(
file_or_bytes=file_or_bytes, filename=filename, ext=ext)

def open(self, mode=None, driver=None, schema=None, crs=None, encoding=None,
layer=None, vfs=None, enabled_drivers=None, crs_wkt=None,
**kwargs):
def open(
self,
mode=None,
driver=None,
schema=None,
crs=None,
encoding=None,
layer=None,
vfs=None,
enabled_drivers=None,
crs_wkt=None,
allow_unsupported_drivers=False,
**kwargs
):
"""Open the file and return a Fiona collection object.
If data has already been written, the file is opened in 'r'
Expand All @@ -51,7 +61,11 @@ def open(self, mode=None, driver=None, schema=None, crs=None, encoding=None,
if self.closed:
raise OSError("I/O operation on closed file.")

if driver is not None and not supports_vsi(driver):
if (
not allow_unsupported_drivers
and driver is not None
and not supports_vsi(driver)
):
raise DriverError("Driver {} does not support virtual files.")

if mode in ('r', 'a') and not self.exists():
Expand All @@ -75,6 +89,7 @@ def open(self, mode=None, driver=None, schema=None, crs=None, encoding=None,
encoding=encoding,
layer=layer,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
crs_wkt=crs_wkt,
**kwargs
)
Expand All @@ -97,8 +112,16 @@ def __init__(self, file_or_bytes=None):
super().__init__(file_or_bytes, ext=".zip")
self.name = "/vsizip{}".format(self.name)

def open(self, path=None, driver=None, encoding=None, layer=None,
enabled_drivers=None, **kwargs):
def open(
self,
path=None,
driver=None,
encoding=None,
layer=None,
enabled_drivers=None,
allow_unsupported_drivers=False,
**kwargs
):
"""Open a dataset within the zipped stream.
Parameters
Expand Down Expand Up @@ -126,5 +149,6 @@ def open(self, path=None, driver=None, encoding=None, layer=None,
encoding=encoding,
layer=layer,
enabled_drivers=enabled_drivers,
allow_unsupported_drivers=allow_unsupported_drivers,
**kwargs
)
31 changes: 31 additions & 0 deletions tests/test_drvsupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,34 @@ def test_mingdal_drivers_are_supported():
# we cannot test drivers that are not present in the gdal installation
if driver in supported_drivers:
assert mode in supported_drivers[driver]


def test_allow_unsupported_drivers(monkeypatch, tmpdir):
"""Test if allow unsupported drivers works as expected"""

# We delete a known working driver from fiona.drvsupport so that we can use it
monkeypatch.delitem(fiona.drvsupport.supported_drivers, "GPKG")

schema = {"geometry": "Polygon", "properties": {}}

# Test that indeed we can't create a file without allow_unsupported_drivers
path1 = str(tmpdir.join("test1.gpkg"))
with pytest.raises(DriverError):
with fiona.open(path1, mode="w", driver="GPKG", schema=schema):
pass

# Test that we can create file with allow_unsupported_drivers=True
path2 = str(tmpdir.join("test2.gpkg"))
try:
with fiona.open(
path2,
mode="w",
driver="GPKG",
schema=schema,
allow_unsupported_drivers=True,
):
pass
except Exception as e:
assert (
False
), f"Using allow_unsupported_drivers=True should not raise an exception: {e}"
27 changes: 27 additions & 0 deletions tests/test_memoryfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,30 @@ def test_read_multilayer_memoryfile(path_coutwildrnp_json, tmpdir):
with fiona.open(f, layer="layer2") as src:
assert src.name == "layer2"
assert len(src) == 62


def test_allow_unsupported_drivers(monkeypatch):
"""Test if allow unsupported drivers works as expected"""

# We delete a known working driver from fiona.drvsupport so that we can use it
monkeypatch.delitem(fiona.drvsupport.supported_drivers, "GPKG")

schema = {"geometry": "Polygon", "properties": {}}

# Test that indeed we can't create a file without allow_unsupported_drivers
with pytest.raises(DriverError):
with MemoryFile() as memfile:
with memfile.open(mode="w", driver="GPKG", schema=schema):
pass

# Test that we can create file with allow_unsupported_drivers=True
try:
with MemoryFile() as memfile:
with memfile.open(
mode="w", driver="GPKG", schema=schema, allow_unsupported_drivers=True
):
pass
except Exception as e:
assert (
False
), f"Using allow_unsupported_drivers=True should not raise an exception: {e}"