[Git][debian-gis-team/fiona][upstream] New upstream version 1.8.1

Bas Couwenberg gitlab at salsa.debian.org
Fri Nov 16 06:38:59 GMT 2018


Bas Couwenberg pushed to branch upstream at Debian GIS Project / fiona


Commits:
652d6524 by Bas Couwenberg at 2018-11-16T05:54:58Z
New upstream version 1.8.1
- - - - -


27 changed files:

- CHANGES.txt
- fiona/__init__.py
- fiona/_drivers.pyx
- fiona/_env.pyx
- fiona/_err.pyx
- fiona/_geometry.pxd
- fiona/_geometry.pyx
- fiona/_shim1.pxd
- fiona/_shim2.pxd
- fiona/_shim2.pyx
- fiona/_shim22.pxd
- fiona/_shim22.pyx
- fiona/collection.py
- fiona/env.py
- fiona/ogrext.pyx
- fiona/ogrext1.pxd
- fiona/ogrext2.pxd
- setup.py
- tests/conftest.py
- + tests/test__env.py
- tests/test_bigint.py
- tests/test_collection.py
- + tests/test_compound_crs.py
- tests/test_datetime.py
- tests/test_env.py
- tests/test_schema.py
- tests/test_unicode.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -3,6 +3,25 @@ Changes
 
 All issue numbers are relative to https://github.com/Toblerity/Fiona/issues.
 
+1.8.1 (2018-11-15)
+------------------
+
+Bug fixes:
+
+- Add checks around OSRGetAuthorityName and OSRGetAuthorityCode calls that will
+  log problems with looking up these items.
+- Opened data sources are now released before we raise exceptions in
+  WritingSession.start (#676). This fixes an issue with locked files on
+  Windows.
+- We now ensure that an Env instance exists when getting the crs or crs_wkt
+  properties of a Collection (#673, #690). Otherwise, required GDAL and PROJ
+  data files included in Fiona wheels can not be found.
+- GDAL and PROJ data search has been refactored to improve testability (#678).
+- In the project's Cython code, void* pointers have been replaced with proper
+  GDAL types (#672).
+- Pervasive warning level log messages about ENCODING creation options (#668)
+  have been eliminated.
+
 1.8.0 (2018-10-31)
 ------------------
 
@@ -74,8 +93,8 @@ Refactoring:
 
 Deprecations:
 
-- The ``rasterio.drivers()`` context manager is officially deprecated. All
-  users should switch to ``rasterio.Env()``, which registers format drivers and
+- The ``fiona.drivers()`` context manager is officially deprecated. All
+  users should switch to ``fiona.Env()``, which registers format drivers and
   manages GDAL configuration in a reversible manner.
 
 Bug fixes:


=====================================
fiona/__init__.py
=====================================
@@ -85,12 +85,12 @@ from fiona.drvsupport import supported_drivers
 from fiona.env import ensure_env_with_credentials, Env
 from fiona.errors import FionaDeprecationWarning
 from fiona._env import driver_count
+from fiona._env import (
+    calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name,
+    get_gdal_version_tuple)
 from fiona.compat import OrderedDict
 from fiona.io import MemoryFile
 from fiona.ogrext import _bounds, _listlayers, FIELD_TYPES_MAP, _remove, _remove_layer
-from fiona.ogrext import (
-    calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name,
-    get_gdal_version_tuple)
 from fiona.path import ParsedPath, parse_path, vsi_path
 from fiona.vfs import parse_paths as vfs_parse_paths
 
@@ -101,12 +101,13 @@ import uuid
 
 
 __all__ = ['bounds', 'listlayers', 'open', 'prop_type', 'prop_width']
-__version__ = "1.8.0"
+__version__ = "1.8.1"
 __gdal_version__ = get_gdal_release_name()
 
 gdal_version = get_gdal_version_tuple()
 
 log = logging.getLogger(__name__)
+log.addHandler(logging.NullHandler())
 
 
 @ensure_env_with_credentials


=====================================
fiona/_drivers.pyx
=====================================
@@ -18,7 +18,8 @@ cdef extern from "cpl_conv.h":
 
 
 cdef extern from "cpl_error.h":
-    void CPLSetErrorHandler (void *handler)
+    ctypedef void (*CPLErrorHandler)(int, int, const char*);
+    void CPLSetErrorHandler (CPLErrorHandler handler)
 
 
 cdef extern from "gdal.h":
@@ -105,7 +106,7 @@ cdef class GDALEnv(object):
             GDALAllRegister()
         if OGRGetDriverCount() == 0:
             OGRRegisterAll()
-        CPLSetErrorHandler(<void *>errorHandler)
+        CPLSetErrorHandler(<CPLErrorHandler>errorHandler)
         if OGRGetDriverCount() == 0:
             raise ValueError("Drivers not registered")
 


=====================================
fiona/_env.pyx
=====================================
@@ -10,6 +10,7 @@ option is set to a new value inside the thread.
 
 include "gdal.pxi"
 
+from collections import namedtuple
 import logging
 import os
 import os.path
@@ -53,14 +54,61 @@ log = logging.getLogger(__name__)
 cdef bint is_64bit = sys.maxsize > 2 ** 32
 
 
+def calc_gdal_version_num(maj, min, rev):
+    """Calculates the internal gdal version number based on major, minor and revision
+
+    GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
+
+    """
+    if (maj, min, rev) >= (1, 10, 0):
+        return int(maj * 1000000 + min * 10000 + rev * 100)
+    else:
+        return int(maj * 1000 + min * 100 + rev * 10)
+
+
+def get_gdal_version_num():
+    """Return current internal version number of gdal"""
+    return int(GDALVersionInfo("VERSION_NUM"))
+
+
+def get_gdal_release_name():
+    """Return release name of gdal"""
+    cdef const char *name_c = NULL
+    name_c = GDALVersionInfo("RELEASE_NAME")
+    name = name_c
+    return name
+
+
+GDALVersion = namedtuple("GDALVersion", ["major", "minor", "revision"])
+
+
+def get_gdal_version_tuple():
+    """
+    Calculates gdal version tuple from gdal's internal version number.
+
+    GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
+    """
+    gdal_version_num = get_gdal_version_num()
+
+    if gdal_version_num >= calc_gdal_version_num(1, 10, 0):
+        major = gdal_version_num // 1000000
+        minor = (gdal_version_num - (major * 1000000)) // 10000
+        revision = (gdal_version_num - (major * 1000000) - (minor * 10000)) // 100
+        return GDALVersion(major, minor, revision)
+    else:
+        major = gdal_version_num // 1000
+        minor = (gdal_version_num - (major * 1000)) // 100
+        revision = (gdal_version_num - (major * 1000) - (minor * 100)) // 10
+        return GDALVersion(major, minor, revision)
+
+
 cdef void log_error(CPLErr err_class, int err_no, const char* msg) with gil:
     """Send CPL debug messages and warnings to Python's logger."""
     log = logging.getLogger(__name__)
-    if err_class < 3:
-        if err_no in code_map:
-            log.log(level_map[err_class], "%s in %s", code_map[err_no], msg)
-        else:
-            log.info("Unknown error number %r", err_no)
+    if err_no in code_map:
+        log.log(level_map[err_class], "%s", msg)
+    else:
+        log.info("Unknown error number %r", err_no)
 
 
 # Definition of GDAL callback functions, one for Windows and one for
@@ -186,6 +234,59 @@ cdef class ConfigEnv(object):
         return {k: get_gdal_config(k) for k in self.options}
 
 
+class GDALDataFinder(object):
+    """Finds GDAL and PROJ data files"""
+
+    def search(self, prefix=None):
+        """Returns GDAL_DATA location"""
+        path = self.search_wheel(prefix or __file__)
+        if not path:
+            path = self.search_prefix(prefix or sys.prefix)
+            if not path:
+                path = self.search_debian(prefix or sys.prefix)
+        return path
+
+    def search_wheel(self, prefix=None):
+        """Check wheel location"""
+        if prefix is None:
+            prefix = __file__
+        datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "gdal_data"))
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+    def search_prefix(self, prefix=sys.prefix):
+        """Check sys.prefix location"""
+        datadir = os.path.join(prefix, 'share', 'gdal')
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+    def search_debian(self, prefix=sys.prefix):
+        """Check Debian locations"""
+        gdal_version = get_gdal_version_tuple()
+        datadir = os.path.join(prefix, 'share', 'gdal', '{}.{}'.format(gdal_version.major, gdal_version.minor))
+        return datadir if os.path.exists(os.path.join(datadir, 'pcs.csv')) else None
+
+
+class PROJDataFinder(object):
+
+    def search(self, prefix=None):
+        """Returns PROJ_LIB location"""
+        path = self.search_wheel(prefix or __file__)
+        if not path:
+            path = self.search_prefix(prefix or sys.prefix)
+        return path
+
+    def search_wheel(self, prefix=None):
+        """Check wheel location"""
+        if prefix is None:
+            prefix = __file__
+        datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "proj_data"))
+        return datadir if os.path.exists(datadir) else None
+
+    def search_prefix(self, prefix=sys.prefix):
+        """Check sys.prefix location"""
+        datadir = os.path.join(prefix, 'share', 'proj')
+        return datadir if os.path.exists(datadir) else None
+
+
 cdef class GDALEnv(ConfigEnv):
     """Configuration and driver management"""
 
@@ -210,40 +311,22 @@ cdef class GDALEnv(ConfigEnv):
 
                     if 'GDAL_DATA' not in os.environ:
 
-                        # We will try a few well-known paths, starting with the
-                        # official wheel path.
-                        whl_datadir = os.path.abspath(
-                            os.path.join(os.path.dirname(__file__), "gdal_data"))
-                        fhs_share_datadir = os.path.join(sys.prefix, 'share/gdal')
-
-                        # Debian supports multiple GDAL installs.
-                        gdal_release_name = GDALVersionInfo("RELEASE_NAME")
-                        deb_share_datadir = os.path.join(
-                            fhs_share_datadir,
-                            "{}.{}".format(*gdal_release_name.split('.')[:2]))
+                        path = GDALDataFinder().search()
 
-                        # If we find GDAL data at the well-known paths, we will
-                        # add a GDAL_DATA key to the config options dict.
-                        if os.path.exists(os.path.join(whl_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=whl_datadir)
+                        if path:
+                            log.debug("GDAL data found in %r", path)
+                            self.update_config_options(GDAL_DATA=path)
 
-                        elif os.path.exists(os.path.join(deb_share_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=deb_share_datadir)
-
-                        elif os.path.exists(os.path.join(fhs_share_datadir, 'pcs.csv')):
-                            self.update_config_options(GDAL_DATA=fhs_share_datadir)
+                    else:
+                        self.update_config_options(GDAL_DATA=os.environ['GDAL_DATA'])
 
                     if 'PROJ_LIB' not in os.environ:
 
-                        whl_datadir = os.path.abspath(
-                            os.path.join(os.path.dirname(__file__), 'proj_data'))
-                        share_datadir = os.path.join(sys.prefix, 'share/proj')
-
-                        if os.path.exists(whl_datadir):
-                            os.environ['PROJ_LIB'] = whl_datadir
+                        path = PROJDataFinder().search()
 
-                        elif os.path.exists(share_datadir):
-                            os.environ['PROJ_LIB'] = share_datadir
+                        if path:
+                            log.debug("PROJ data found in %r", path)
+                            os.environ['PROJ_LIB'] = path
 
                     if driver_count() == 0:
                         CPLPopErrorHandler()
@@ -274,8 +357,8 @@ cdef class GDALEnv(ConfigEnv):
         result = {}
         for i in range(OGRGetDriverCount()):
             drv = OGRGetDriver(i)
-            key = OGR_Dr_GetName(drv)
-            val = OGR_Dr_GetName(drv)
+            key = <char *>OGR_Dr_GetName(drv)
+            val = <char *>OGR_Dr_GetName(drv)
             result[key] = val
 
         return result


=====================================
fiona/_err.pyx
=====================================
@@ -66,7 +66,7 @@ class CPLE_BaseError(Exception):
         return self.__unicode__()
 
     def __unicode__(self):
-        return "{}".format(self.errmsg)
+        return u"{}".format(self.errmsg)
 
     @property
     def args(self):
@@ -203,7 +203,7 @@ cdef class GDALErrCtxManager:
 
 cdef inline object exc_check():
     """Checks GDAL error stack for fatal or non-fatal errors
-    
+
     Returns
     -------
     An Exception, SystemExit, or None


=====================================
fiona/_geometry.pxd
=====================================
@@ -3,6 +3,81 @@
 ctypedef int OGRErr
 
 
+cdef extern from "ogr_core.h":
+    ctypedef enum OGRwkbGeometryType:
+        wkbUnknown
+        wkbPoint
+        wkbLineString
+        wkbPolygon
+        wkbMultiPoint
+        wkbMultiLineString
+        wkbMultiPolygon
+        wkbGeometryCollection
+        wkbCircularString
+        wkbCompoundCurve
+        wkbCurvePolygon
+        wkbMultiCurve
+        wkbMultiSurface
+        wkbCurve
+        wkbSurface
+        wkbPolyhedralSurface
+        wkbTIN
+        wkbTriangle
+        wkbNone
+        wkbLinearRing
+        wkbCircularStringZ
+        wkbCompoundCurveZ
+        wkbCurvePolygonZ
+        wkbMultiCurveZ
+        wkbMultiSurfaceZ
+        wkbCurveZ
+        wkbSurfaceZ
+        wkbPolyhedralSurfaceZ
+        wkbTINZ
+        wkbTriangleZ
+        wkbPointM
+        wkbLineStringM
+        wkbPolygonM
+        wkbMultiPointM
+        wkbMultiLineStringM
+        wkbMultiPolygonM
+        wkbGeometryCollectionM
+        wkbCircularStringM
+        wkbCompoundCurveM
+        wkbCurvePolygonM
+        wkbMultiCurveM
+        wkbMultiSurfaceM
+        wkbCurveM
+        wkbSurfaceM
+        wkbPolyhedralSurfaceM
+        wkbTINM
+        wkbTriangleM
+        wkbPointZM
+        wkbLineStringZM
+        wkbPolygonZM
+        wkbMultiPointZM
+        wkbMultiLineStringZM
+        wkbMultiPolygonZM
+        wkbGeometryCollectionZM
+        wkbCircularStringZM
+        wkbCompoundCurveZM
+        wkbCurvePolygonZM
+        wkbMultiCurveZM
+        wkbMultiSurfaceZM
+        wkbCurveZM
+        wkbSurfaceZM
+        wkbPolyhedralSurfaceZM
+        wkbTINZM
+        wkbTriangleZM
+        wkbPoint25D
+        wkbLineString25D
+        wkbPolygon25D
+        wkbMultiPoint25D
+        wkbMultiLineString25D
+        wkbMultiPolygon25D
+        wkbGeometryCollection25D
+
+
 ctypedef struct OGREnvelope:
     double MinX
     double MaxX
@@ -15,7 +90,7 @@ cdef extern from "ogr_api.h":
     void    OGR_G_AddPoint (void *geometry, double x, double y, double z)
     void    OGR_G_AddPoint_2D (void *geometry, double x, double y)
     void    OGR_G_CloseRings (void *geometry)
-    void *  OGR_G_CreateGeometry (int wkbtypecode)
+    void *  OGR_G_CreateGeometry (OGRwkbGeometryType wkbtypecode)
     void    OGR_G_DestroyGeometry (void *geometry)
     unsigned char * OGR_G_ExportToJson (void *geometry)
     void    OGR_G_ExportToWkb (void *geometry, int endianness, char *buffer)


=====================================
fiona/_geometry.pyx
=====================================
@@ -85,7 +85,7 @@ cdef void * _createOgrGeomFromWKB(object wkb) except NULL:
     """Make an OGR geometry from a WKB string"""
     wkbtype = bytearray(wkb)[1]
     cdef unsigned char *buffer = wkb
-    cdef void *cogr_geometry = OGR_G_CreateGeometry(wkbtype)
+    cdef void *cogr_geometry = OGR_G_CreateGeometry(<OGRwkbGeometryType>wkbtype)
     if cogr_geometry is not NULL:
         OGR_G_ImportFromWkb(cogr_geometry, buffer, len(wkb))
     return cogr_geometry
@@ -191,7 +191,7 @@ cdef class OGRGeomBuilder:
     """Builds OGR geometries from Fiona geometries.
     """
     cdef void * _createOgrGeometry(self, int geom_type) except NULL:
-        cdef void *cogr_geometry = OGR_G_CreateGeometry(geom_type)
+        cdef void *cogr_geometry = OGR_G_CreateGeometry(<OGRwkbGeometryType>geom_type)
         if cogr_geometry == NULL:
             raise Exception("Could not create OGR Geometry of type: %i" % geom_type)
         return cogr_geometry
@@ -286,11 +286,6 @@ cdef class OGRGeomBuilder:
             raise ValueError("Unsupported geometry type %s" % typename)
 
 
-cdef geometry(void *geom):
-    """Factory for Fiona geometries"""
-    return GeomBuilder().build(geom)
-
-
 def geometryRT(geometry):
     # For testing purposes only, leaks the JSON data
     cdef void *cogr_geometry = OGRGeomBuilder().build(geometry)


=====================================
fiona/_shim1.pxd
=====================================
@@ -10,7 +10,7 @@ ctypedef enum OGRFieldSubType:
 cdef bint is_field_null(void *feature, int n)
 cdef void set_field_null(void *feature, int n)
 cdef void gdal_flush_cache(void *cogr_ds)
-cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NULL
+cdef void* gdal_open_vector(const char* path_c, int mode, drivers, options) except NULL
 cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NULL
 cdef OGRErr gdal_start_transaction(void *cogr_ds, int force)
 cdef OGRErr gdal_commit_transaction(void *cogr_ds)


=====================================
fiona/_shim2.pxd
=====================================
@@ -3,7 +3,7 @@ include "ogrext2.pxd"
 cdef bint is_field_null(void *feature, int n)
 cdef void set_field_null(void *feature, int n)
 cdef void gdal_flush_cache(void *cogr_ds)
-cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NULL
+cdef void* gdal_open_vector(const char* path_c, int mode, drivers, options) except NULL
 cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NULL
 cdef OGRErr gdal_start_transaction(void *cogr_ds, int force)
 cdef OGRErr gdal_commit_transaction(void *cogr_ds)


=====================================
fiona/_shim2.pyx
=====================================
@@ -25,7 +25,7 @@ cdef void gdal_flush_cache(void *cogr_ds):
         GDALFlushCache(cogr_ds)
 
 
-cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NULL:
+cdef void* gdal_open_vector(const char* path_c, int mode, drivers, options) except NULL:
     cdef void* cogr_ds = NULL
     cdef char **drvs = NULL
     cdef char **open_opts = NULL


=====================================
fiona/_shim22.pxd
=====================================
@@ -3,7 +3,7 @@ include "ogrext2.pxd"
 cdef bint is_field_null(void *feature, int n)
 cdef void set_field_null(void *feature, int n)
 cdef void gdal_flush_cache(void *cogr_ds)
-cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NULL
+cdef void* gdal_open_vector(const char *path_c, int mode, drivers, options) except NULL
 cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NULL
 cdef OGRErr gdal_start_transaction(void *cogr_ds, int force)
 cdef OGRErr gdal_commit_transaction(void *cogr_ds)


=====================================
fiona/_shim22.pyx
=====================================
@@ -30,7 +30,7 @@ cdef void gdal_flush_cache(void *cogr_ds):
         GDALFlushCache(cogr_ds)
 
 
-cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NULL:
+cdef void* gdal_open_vector(const char* path_c, int mode, drivers, options) except NULL:
     cdef void* cogr_ds = NULL
     cdef char **drvs = NULL
     cdef void* drv = NULL
@@ -64,7 +64,7 @@ cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NUL
 
     try:
         cogr_ds = exc_wrap_pointer(
-            GDALOpenEx(path_c, flags, <const char *const *>drvs, open_opts, NULL)
+            GDALOpenEx(path_c, flags, <const char *const *>drvs, <const char *const *>open_opts, NULL)
         )
         return cogr_ds
     except FionaNullPointerError:


=====================================
fiona/collection.py
=====================================
@@ -8,12 +8,12 @@ import warnings
 from fiona import compat, vfs
 from fiona.ogrext import Iterator, ItemsIterator, KeysIterator
 from fiona.ogrext import Session, WritingSession
-from fiona.ogrext import (
-    calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name)
 from fiona.ogrext import buffer_to_virtual_file, remove_virtual_file, GEOMETRY_TYPES
 from fiona.errors import (DriverError, SchemaError, CRSError, UnsupportedGeometryTypeError, DriverSupportError)
 from fiona.logutils import FieldSkipLogFilter
 from fiona._env import driver_count
+from fiona._env import (
+    calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name)
 from fiona.env import Env
 from fiona.errors import FionaDeprecationWarning
 from fiona.drvsupport import supported_drivers


=====================================
fiona/env.py
=====================================
@@ -1,5 +1,6 @@
 """Fiona's GDAL/AWS environment"""
 
+from contextlib import contextmanager
 from functools import wraps, total_ordering
 import logging
 import re
@@ -9,7 +10,8 @@ import attr
 from six import string_types
 
 from fiona._env import (
-    GDALEnv, get_gdal_config, set_gdal_config)
+    GDALEnv, calc_gdal_version_num, get_gdal_version_num, get_gdal_config,
+    set_gdal_config, get_gdal_release_name)
 from fiona.compat import getargspec
 from fiona.errors import EnvError, GDALVersionError
 from fiona.session import Session, DummySession
@@ -160,15 +162,16 @@ class Env(object):
         self.context_options = {}
 
     @classmethod
-    def from_defaults(cls, *args, **kwargs):
+    def from_defaults(cls, session=None, **options):
         """Create an environment with default config options
 
         Parameters
         ----------
-        args : optional
-            Positional arguments for Env()
-        kwargs : optional
-            Keyword arguments for Env()
+        session : optional
+            A Session object.
+        **options : optional
+            A mapping of GDAL configuration options, e.g.,
+            `CPL_DEBUG=True, CHECK_WITH_INVERT_PROJ=False`.
 
         Returns
         -------
@@ -179,9 +182,9 @@ class Env(object):
         The items in kwargs will be overlaid on the default values.
 
         """
-        options = Env.default_options()
-        options.update(**kwargs)
-        return Env(*args, **options)
+        opts = Env.default_options()
+        opts.update(**options)
+        return Env(session=session, **opts)
 
     @property
     def is_credentialized(self):
@@ -313,16 +316,55 @@ def delenv():
     local._env = None
 
 
+class NullContextManager(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __exit__(self, *args):
+        pass
+
+
+def env_ctx_if_needed():
+    """Return an Env if one does not exist
+
+    Returns
+    -------
+    Env or a do-nothing context manager
+
+    """
+    if local._env:
+        return NullContextManager()
+    else:
+        return Env.from_defaults()
+
+
 def ensure_env(f):
     """A decorator that ensures an env exists before a function
-    calls any GDAL C functions."""
+    calls any GDAL C functions.
+
+    Parameters
+    ----------
+    f : function
+        A function.
+
+    Returns
+    -------
+    A function wrapper.
+
+    Notes
+    -----
+    If there is already an existing environment, the wrapper does
+    nothing and immediately calls f with the given arguments.
+
+    """
     @wraps(f)
-    def wrapper(*args, **kwds):
+    def wrapper(*args, **kwargs):
         if local._env:
-            return f(*args, **kwds)
+            return f(*args, **kwargs)
         else:
             with Env.from_defaults():
-                return f(*args, **kwds)
+                return f(*args, **kwargs)
     return wrapper
 
 
@@ -344,25 +386,23 @@ def ensure_env_with_credentials(f):
     credentializes the environment if the first argument is a URI with
     scheme "s3".
 
+    If there is already an existing environment, the wrapper does
+    nothing and immediately calls f with the given arguments.
+
     """
     @wraps(f)
-    def wrapper(*args, **kwds):
+    def wrapper(*args, **kwargs):
         if local._env:
-            env_ctor = Env
-        else:
-            env_ctor = Env.from_defaults
-
-        if hascreds():
-            session = DummySession()
-        elif isinstance(args[0], str):
-            session = Session.from_path(args[0])
+            return f(*args, **kwargs)
         else:
-            session = Session.from_path(None)
-
-        with env_ctor(session=session):
-            log.debug("Credentialized: {!r}".format(getenv()))
-            return f(*args, **kwds)
-
+            if isinstance(args[0], str):
+                session = Session.from_path(args[0])
+            else:
+                session = Session.from_path(None)
+
+            with Env.from_defaults(session=session):
+                log.debug("Credentialized: {!r}".format(getenv()))
+                return f(*args, **kwargs)
     return wrapper
 
 
@@ -425,7 +465,6 @@ class GDALVersion(object):
     @classmethod
     def runtime(cls):
         """Return GDALVersion of current GDAL runtime"""
-        from fiona.ogrext import get_gdal_release_name  # to avoid circular import
         return cls.parse(get_gdal_release_name())
 
     def at_least(self, other):


=====================================
fiona/ogrext.pyx
=====================================
@@ -22,7 +22,9 @@ from fiona._geometry cimport (
 from fiona._err cimport exc_wrap_int, exc_wrap_pointer, exc_wrap_vsilfile
 
 import fiona
-from fiona._err import cpl_errs, FionaNullPointerError, CPLE_BaseError
+from fiona.env import env_ctx_if_needed
+from fiona._env import GDALVersion, get_gdal_version_num
+from fiona._err import cpl_errs, FionaNullPointerError, CPLE_BaseError, CPLE_OpenFailedError
 from fiona._geometry import GEOMETRY_TYPES
 from fiona import compat
 from fiona.errors import (
@@ -102,54 +104,8 @@ def _bounds(geometry):
         return None
 
 
-def calc_gdal_version_num(maj, min, rev):
-    """Calculates the internal gdal version number based on major, minor and revision
-
-    GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
-
-    """
-    if (maj, min, rev) >= (1, 10, 0):
-        return int(maj * 1000000 + min * 10000 + rev * 100)
-    else:
-        return int(maj * 1000 + min * 100 + rev * 10)
-
-
-def get_gdal_version_num():
-    """Return current internal version number of gdal"""
-    return int(GDALVersionInfo("VERSION_NUM"))
-
-
-def get_gdal_release_name():
-    """Return release name of gdal"""
-    cdef const char *name_c = NULL
-    name_c = GDALVersionInfo("RELEASE_NAME")
-    name_b = name_c
-    return name_b.decode('utf-8')
-
-
 cdef int GDAL_VERSION_NUM = get_gdal_version_num()
 
-GDALVersion = namedtuple("GDALVersion", ["major", "minor", "revision"])
-
-
-def get_gdal_version_tuple():
-    """
-    Calculates gdal version tuple from gdal's internal version number.
-    
-    GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
-    """
-    gdal_version_num = get_gdal_version_num()
-
-    if gdal_version_num >= calc_gdal_version_num(1, 10, 0):
-        major = gdal_version_num // 1000000
-        minor = (gdal_version_num - (major * 1000000)) // 10000
-        revision = (gdal_version_num - (major * 1000000) - (minor * 10000)) // 100
-        return GDALVersion(major, minor, revision)
-    else:
-        major = gdal_version_num // 1000
-        minor = (gdal_version_num - (major * 1000)) // 100
-        revision = (gdal_version_num - (major * 1000) - (minor * 100)) // 10
-        return GDALVersion(major, minor, revision)     
 
 # Feature extension classes and functions follow.
 
@@ -520,7 +476,7 @@ cdef class Session:
             OGR_L_TestCapability(
                 self.cogr_layer, OLC_STRINGSASUTF8) and
             'utf-8') or (
-            self.get_driver() == "ESRI Shapefile" and
+            "Shapefile" in self.get_driver() and
             'ISO-8859-1') or locale.getpreferredencoding().upper()
 
         if collection.ignore_fields:
@@ -531,7 +487,7 @@ cdef class Session:
                     except AttributeError:
                         raise TypeError("Ignored field \"{}\" has type \"{}\", expected string".format(name, name.__class__.__name__))
                     ignore_fields = CSLAddString(ignore_fields, <const char *>name)
-                OGR_L_SetIgnoredFields(self.cogr_layer, ignore_fields)
+                OGR_L_SetIgnoredFields(self.cogr_layer, <const char**>ignore_fields)
             finally:
                 CSLDestroy(ignore_fields)
 
@@ -650,79 +606,135 @@ cdef class Session:
         return ret
 
     def get_crs(self):
+        """Get the layer's CRS
+
+        Returns
+        -------
+        CRS
+
+        """
         cdef char *proj_c = NULL
         cdef const char *auth_key = NULL
         cdef const char *auth_val = NULL
         cdef void *cogr_crs = NULL
+
         if self.cogr_layer == NULL:
             raise ValueError("Null layer")
-        cogr_crs = OGR_L_GetSpatialRef(self.cogr_layer)
-        crs = {}
-        if cogr_crs is not NULL:
-            log.debug("Got coordinate system")
-
-            retval = OSRAutoIdentifyEPSG(cogr_crs)
-            if retval > 0:
-                log.info("Failed to auto identify EPSG: %d", retval)
-
-            auth_key = OSRGetAuthorityName(cogr_crs, NULL)
-            auth_val = OSRGetAuthorityCode(cogr_crs, NULL)
-
-            if auth_key != NULL and auth_val != NULL:
-                key_b = auth_key
-                key = key_b.decode('utf-8')
-                if key == 'EPSG':
-                    val_b = auth_val
-                    val = val_b.decode('utf-8')
-                    crs['init'] = "epsg:" + val
-            else:
-                OSRExportToProj4(cogr_crs, &proj_c)
-                if proj_c == NULL:
-                    raise ValueError("Null projection")
-                proj_b = proj_c
-                log.debug("Params: %s", proj_b)
-                value = proj_b.decode()
-                value = value.strip()
-                for param in value.split():
-                    kv = param.split("=")
-                    if len(kv) == 2:
-                        k, v = kv
-                        try:
-                            v = float(v)
-                            if v % 1 == 0:
-                                v = int(v)
-                        except ValueError:
-                            # Leave v as a string
-                            pass
-                    elif len(kv) == 1:
-                        k, v = kv[0], True
+
+        # We can't simply wrap a method in Python 2.7 so we
+        # bring the context manager inside like so.
+        with env_ctx_if_needed():
+
+            try:
+                cogr_crs = exc_wrap_pointer(OGR_L_GetSpatialRef(self.cogr_layer))
+            # TODO: we don't intend to use try/except for flow control
+            # this is a work around for a GDAL issue.
+            except FionaNullPointerError:
+                log.debug("Layer has no coordinate system")
+
+            if cogr_crs is not NULL:
+
+                log.debug("Got coordinate system")
+                crs = {}
+
+                try:
+
+                    retval = OSRAutoIdentifyEPSG(cogr_crs)
+                    if retval > 0:
+                        log.info("Failed to auto identify EPSG: %d", retval)
+
+                    try:
+                        auth_key = <const char *>exc_wrap_pointer(<void *>OSRGetAuthorityName(cogr_crs, NULL))
+                        auth_val = <const char *>exc_wrap_pointer(<void *>OSRGetAuthorityCode(cogr_crs, NULL))
+
+                    except CPLE_BaseError as exc:
+                        log.debug("{}".format(exc))
+
+                    if auth_key != NULL and auth_val != NULL:
+                        key_b = auth_key
+                        key = key_b.decode('utf-8')
+                        if key == 'EPSG':
+                            val_b = auth_val
+                            val = val_b.decode('utf-8')
+                            crs['init'] = "epsg:" + val
+
                     else:
-                        raise ValueError("Unexpected proj parameter %s" % param)
-                    k = k.lstrip("+")
-                    crs[k] = v
+                        OSRExportToProj4(cogr_crs, &proj_c)
+                        if proj_c == NULL:
+                            raise ValueError("Null projection")
+                        proj_b = proj_c
+                        log.debug("Params: %s", proj_b)
+                        value = proj_b.decode()
+                        value = value.strip()
+                        for param in value.split():
+                            kv = param.split("=")
+                            if len(kv) == 2:
+                                k, v = kv
+                                try:
+                                    v = float(v)
+                                    if v % 1 == 0:
+                                        v = int(v)
+                                except ValueError:
+                                    # Leave v as a string
+                                    pass
+                            elif len(kv) == 1:
+                                k, v = kv[0], True
+                            else:
+                                raise ValueError("Unexpected proj parameter %s" % param)
+                            k = k.lstrip("+")
+                            crs[k] = v
 
-            CPLFree(proj_c)
-        else:
-            log.debug("Projection not found (cogr_crs was NULL)")
-        return crs
+                finally:
+                    CPLFree(proj_c)
+                    return crs
+
+            else:
+                log.debug("Projection not found (cogr_crs was NULL)")
+
+        return {}
 
     def get_crs_wkt(self):
         cdef char *proj_c = NULL
+        cdef void *cogr_crs = NULL
+
         if self.cogr_layer == NULL:
             raise ValueError("Null layer")
-        cogr_crs = OGR_L_GetSpatialRef(self.cogr_layer)
-        crs_wkt = ""
-        if cogr_crs is not NULL:
-            log.debug("Got coordinate system")
-            OSRExportToWkt(cogr_crs, &proj_c)
-            if proj_c == NULL:
-                raise ValueError("Null projection")
-            proj_b = proj_c
-            crs_wkt = proj_b.decode('utf-8')
-            CPLFree(proj_c)
-        else:
-            log.debug("Projection not found (cogr_crs was NULL)")
-        return crs_wkt
+
+        # We can't simply wrap a method in Python 2.7 so we
+        # bring the context manager inside like so.
+        with env_ctx_if_needed():
+
+            try:
+                cogr_crs = exc_wrap_pointer(OGR_L_GetSpatialRef(self.cogr_layer))
+
+            # TODO: we don't intend to use try/except for flow control
+            # this is a work around for a GDAL issue.
+            except FionaNullPointerError:
+                log.debug("Layer has no coordinate system")
+            except fiona._err.CPLE_OpenFailedError as exc:
+                log.debug("A support file wasn't opened. See the preceding ERROR level message.")
+                cogr_crs = OGR_L_GetSpatialRef(self.cogr_layer)
+                log.debug("Called OGR_L_GetSpatialRef() again without error checking.")
+                if cogr_crs == NULL:
+                    raise exc
+
+            if cogr_crs is not NULL:
+                log.debug("Got coordinate system")
+
+                try:
+                    OSRExportToWkt(cogr_crs, &proj_c)
+                    if proj_c == NULL:
+                        raise ValueError("Null projection")
+                    proj_b = proj_c
+                    crs_wkt = proj_b.decode('utf-8')
+
+                finally:
+                    CPLFree(proj_c)
+                    return crs_wkt
+
+            else:
+                log.debug("Projection not found (cogr_crs was NULL)")
+                return ""
 
     def get_extent(self):
         cdef OGREnvelope extent
@@ -822,46 +834,49 @@ cdef class WritingSession(Session):
         cdef const char *proj_c = NULL
         cdef const char *fileencoding_c = NULL
         cdef OGRFieldSubType field_subtype
+        cdef int ret
         path = collection.path
         self.collection = collection
 
         userencoding = kwargs.get('encoding')
 
         if collection.mode == 'a':
-            if os.path.exists(path):
-                try:
-                    path_b = path.encode('utf-8')
-                except UnicodeDecodeError:
-                    path_b = path
-                path_c = path_b
-                self.cogr_ds = gdal_open_vector(path_c, 1, None, kwargs)
 
-                cogr_driver = GDALGetDatasetDriver(self.cogr_ds)
-                if cogr_driver == NULL:
-                    raise ValueError("Null driver")
+            if not os.path.exists(path):
+                raise OSError("No such file or directory %s" % path)
+
+            try:
+                path_b = path.encode('utf-8')
+            except UnicodeDecodeError:
+                path_b = path
+            path_c = path_b
+
+            try:
+                self.cogr_ds = gdal_open_vector(path_c, 1, None, kwargs)
 
                 if isinstance(collection.name, string_types):
                     name_b = collection.name.encode()
                     name_c = name_b
-                    self.cogr_layer = GDALDatasetGetLayerByName(
-                                        self.cogr_ds, name_c)
+                    self.cogr_layer = exc_wrap_pointer(GDALDatasetGetLayerByName(self.cogr_ds, name_c))
+
                 elif isinstance(collection.name, int):
-                    self.cogr_layer = GDALDatasetGetLayer(
-                                        self.cogr_ds, collection.name)
+                    self.cogr_layer = exc_wrap_pointer(GDALDatasetGetLayer(self.cogr_ds, collection.name))
 
-                if self.cogr_layer == NULL:
-                    raise RuntimeError(
-                        "Failed to get layer %s" % collection.name)
-            else:
-                raise OSError("No such file or directory %s" % path)
+            except CPLE_BaseError as exc:
+                OGRReleaseDataSource(self.cogr_ds)
+                self.cogr_ds = NULL
+                self.cogr_layer = NULL
+                raise DriverError(u"{}".format(exc))
 
-            self._fileencoding = (userencoding or (
-                OGR_L_TestCapability(self.cogr_layer, OLC_STRINGSASUTF8) and
-                'utf-8') or (
-                self.get_driver() == "ESRI Shapefile" and
-                'ISO-8859-1') or locale.getpreferredencoding()).upper()
+            else:
+                self._fileencoding = (userencoding or (
+                    OGR_L_TestCapability(self.cogr_layer, OLC_STRINGSASUTF8) and
+                    'utf-8') or (
+                    self.get_driver() == "ESRI Shapefile" and
+                    'ISO-8859-1') or locale.getpreferredencoding()).upper()
 
         elif collection.mode == 'w':
+
             try:
                 path_b = path.encode('utf-8')
             except UnicodeDecodeError:
@@ -870,11 +885,7 @@ cdef class WritingSession(Session):
 
             driver_b = collection.driver.encode()
             driver_c = driver_b
-
-
-            cogr_driver = GDALGetDriverByName(driver_c)
-            if cogr_driver == NULL:
-                raise ValueError("Null driver")
+            cogr_driver = exc_wrap_pointer(GDALGetDriverByName(driver_c))
 
             # Our most common use case is the creation of a new data
             # file and historically we've assumed that it's a file on
@@ -883,7 +894,7 @@ cdef class WritingSession(Session):
             # TODO: remove the assumption.
             if not os.path.exists(path):
                 log.debug("File doesn't exist. Creating a new one...")
-                cogr_ds = gdal_create(cogr_driver, path_c, kwargs)
+                cogr_ds = gdal_create(cogr_driver, path_c, {})
 
             # TODO: revisit the logic in the following blocks when we
             # change the assumption above.
@@ -915,59 +926,68 @@ cdef class WritingSession(Session):
             # collection constructor. We by-pass the crs_wkt and crs
             # properties because they aren't accessible until the layer
             # is constructed (later).
-            col_crs = collection._crs_wkt or collection._crs
-            if col_crs:
-                cogr_srs = OSRNewSpatialReference(NULL)
-                if cogr_srs == NULL:
-                    raise ValueError("NULL spatial reference")
-                # First, check for CRS strings like "EPSG:3857".
-                if isinstance(col_crs, string_types):
-                    proj_b = col_crs.encode('utf-8')
-                    proj_c = proj_b
-                    OSRSetFromUserInput(cogr_srs, proj_c)
-                elif isinstance(col_crs, compat.DICT_TYPES):
-                    # EPSG is a special case.
-                    init = col_crs.get('init')
-                    if init:
-                        log.debug("Init: %s", init)
-                        auth, val = init.split(':')
-                        if auth.upper() == 'EPSG':
-                            log.debug("Setting EPSG: %s", val)
-                            OSRImportFromEPSG(cogr_srs, int(val))
-                    else:
-                        params = []
-                        col_crs['wktext'] = True
-                        for k, v in col_crs.items():
-                            if v is True or (k in ('no_defs', 'wktext') and v):
-                                params.append("+%s" % k)
-                            else:
-                                params.append("+%s=%s" % (k, v))
-                        proj = " ".join(params)
-                        log.debug("PROJ.4 to be imported: %r", proj)
-                        proj_b = proj.encode('utf-8')
+            try:
+
+                col_crs = collection._crs_wkt or collection._crs
+
+                if col_crs:
+                    cogr_srs = exc_wrap_pointer(OSRNewSpatialReference(NULL))
+
+                    # First, check for CRS strings like "EPSG:3857".
+                    if isinstance(col_crs, string_types):
+                        proj_b = col_crs.encode('utf-8')
                         proj_c = proj_b
-                        OSRImportFromProj4(cogr_srs, proj_c)
-                else:
-                    raise ValueError("Invalid CRS")
+                        OSRSetFromUserInput(cogr_srs, proj_c)
+
+                    elif isinstance(col_crs, compat.DICT_TYPES):
+                        # EPSG is a special case.
+                        init = col_crs.get('init')
+                        if init:
+                            log.debug("Init: %s", init)
+                            auth, val = init.split(':')
+                            if auth.upper() == 'EPSG':
+                                log.debug("Setting EPSG: %s", val)
+                                OSRImportFromEPSG(cogr_srs, int(val))
+                        else:
+                            params = []
+                            col_crs['wktext'] = True
+                            for k, v in col_crs.items():
+                                if v is True or (k in ('no_defs', 'wktext') and v):
+                                    params.append("+%s" % k)
+                                else:
+                                    params.append("+%s=%s" % (k, v))
+                            proj = " ".join(params)
+                            log.debug("PROJ.4 to be imported: %r", proj)
+                            proj_b = proj.encode('utf-8')
+                            proj_c = proj_b
+                            OSRImportFromProj4(cogr_srs, proj_c)
+
+                    else:
+                        raise ValueError("Invalid CRS")
+
+                    # Fixup, export to WKT, and set the GDAL dataset's projection.
+                    OSRFixup(cogr_srs)
+
+            except (ValueError, CPLE_BaseError) as exc:
+                OGRReleaseDataSource(self.cogr_ds)
+                self.cogr_ds = NULL
+                self.cogr_layer = NULL
+                raise CRSError(u"{}".format(exc))
 
-                # Fixup, export to WKT, and set the GDAL dataset's projection.
-                OSRFixup(cogr_srs)
 
             # Figure out what encoding to use. The encoding parameter given
             # to the collection constructor takes highest precedence, then
             # 'iso-8859-1', then the system's default encoding as last resort.
             sysencoding = locale.getpreferredencoding()
             self._fileencoding = (userencoding or (
-                collection.driver == "ESRI Shapefile" and
+                "Shapefile" in collection.driver and
                 'ISO-8859-1') or sysencoding).upper()
 
-            # The ENCODING option makes no sense for some drivers and
-            # will result in a warning. Fixing is a TODO.
-            fileencoding = self.get_fileencoding()
-            if fileencoding:
-                fileencoding_b = fileencoding.encode('utf-8')
-                fileencoding_c = fileencoding_b
-                with cpl_errs:
+            if "Shapefile" in collection.driver:
+                fileencoding = self.get_fileencoding()
+                if fileencoding:
+                    fileencoding_b = fileencoding.encode('utf-8')
+                    fileencoding_c = fileencoding_b
                     options = CSLSetNameValue(options, "ENCODING", fileencoding_c)
 
             # Does the layer exist already? If so, we delete it.
@@ -995,7 +1015,14 @@ cdef class WritingSession(Session):
             name_c = name_b
 
             for k, v in kwargs.items():
+
+                # We need to remove encoding from the layer creation
+                # options if we're not creating a shapefile.
+                if k == 'encoding' and "Shapefile" not in collection.driver:
+                    continue
+
                 k = k.upper().encode('utf-8')
+
                 if isinstance(v, bool):
                     v = ('ON' if v else 'OFF').encode('utf-8')
                 else:
@@ -1018,9 +1045,13 @@ cdef class WritingSession(Session):
                 self.cogr_layer = exc_wrap_pointer(
                     GDALDatasetCreateLayer(
                         self.cogr_ds, name_c, cogr_srs,
-                        geometry_code, options))
+                        <OGRwkbGeometryType>geometry_code, options))
+
             except Exception as exc:
-                raise DriverIOError(str(exc))
+                OGRReleaseDataSource(self.cogr_ds)
+                self.cogr_ds = NULL
+                raise DriverIOError(u"{}".format(exc))
+
             finally:
                 if options != NULL:
                     CSLDestroy(options)
@@ -1032,9 +1063,6 @@ cdef class WritingSession(Session):
                 if cogr_srs != NULL:
                     OSRRelease(cogr_srs)
 
-            if self.cogr_layer == NULL:
-                raise ValueError("Null layer")
-
             log.debug("Created layer %s", collection.name)
 
             # Next, make a layer definition from the given schema properties,
@@ -1076,29 +1104,26 @@ cdef class WritingSession(Session):
 
                 field_type = FIELD_TYPES.index(value)
                 encoding = self.get_internalencoding()
-                key_bytes = key.encode(encoding)
-
-                cogr_fielddefn = exc_wrap_pointer(OGR_Fld_Create(key_bytes, field_type))
-
-                if cogr_fielddefn == NULL:
-                    raise ValueError("Field {} definition is NULL".format(key))
-
-                if width:
-                    OGR_Fld_SetWidth(cogr_fielddefn, width)
-                if precision:
-                    OGR_Fld_SetPrecision(cogr_fielddefn, precision)
-                if field_subtype != OFSTNone:
-                    # subtypes are new in GDAL 2.x, ignored in 1.x
-                    set_field_subtype(cogr_fielddefn, field_subtype)
-
                 try:
+                    key_bytes = key.encode(encoding)
+                    cogr_fielddefn = exc_wrap_pointer(OGR_Fld_Create(key_bytes, <OGRFieldType>field_type))
+                    if width:
+                        OGR_Fld_SetWidth(cogr_fielddefn, width)
+                    if precision:
+                        OGR_Fld_SetPrecision(cogr_fielddefn, precision)
+                    if field_subtype != OFSTNone:
+                        # subtypes are new in GDAL 2.x, ignored in 1.x
+                        set_field_subtype(cogr_fielddefn, field_subtype)
                     exc_wrap_int(OGR_L_CreateField(self.cogr_layer, cogr_fielddefn, 1))
-                except CPLE_BaseError as exc:
-                    raise SchemaError(str(exc))
-
-                OGR_Fld_Destroy(cogr_fielddefn)
+                except (UnicodeEncodeError, CPLE_BaseError) as exc:
+                    OGRReleaseDataSource(self.cogr_ds)
+                    self.cogr_ds = NULL
+                    self.cogr_layer = NULL
+                    raise SchemaError(u"{}".format(exc))
 
-                log.debug("End creating field %r", key)
+                else:
+                    OGR_Fld_Destroy(cogr_fielddefn)
+                    log.debug("End creating field %r", key)
 
         # Mapping of the Python collection schema to the munged
         # OGR schema.


=====================================
fiona/ogrext1.pxd
=====================================
@@ -52,6 +52,94 @@ ctypedef struct OGREnvelope:
     double MaxY
 
 cdef extern from "ogr_core.h":
+    ctypedef enum OGRwkbGeometryType:
+        wkbUnknown
+        wkbPoint
+        wkbLineString
+        wkbPolygon
+        wkbMultiPoint
+        wkbMultiLineString
+        wkbMultiPolygon
+        wkbGeometryCollection
+        wkbCircularString
+        wkbCompoundCurve
+        wkbCurvePolygon
+        wkbMultiCurve
+        wkbMultiSurface
+        wkbCurve
+        wkbSurface
+        wkbPolyhedralSurface
+        wkbTIN
+        wkbTriangle
+        wkbNone
+        wkbLinearRing
+        wkbCircularStringZ
+        wkbCompoundCurveZ
+        wkbCurvePolygonZ
+        wkbMultiCurveZ
+        wkbMultiSurfaceZ
+        wkbCurveZ
+        wkbSurfaceZ
+        wkbPolyhedralSurfaceZ
+        wkbTINZ
+        wkbTriangleZ
+        wkbPointM
+        wkbLineStringM
+        wkbPolygonM
+        wkbMultiPointM
+        wkbMultiLineStringM
+        wkbMultiPolygonM
+        wkbGeometryCollectionM
+        wkbCircularStringM
+        wkbCompoundCurveM
+        wkbCurvePolygonM
+        wkbMultiCurveM
+        wkbMultiSurfaceM
+        wkbCurveM
+        wkbSurfaceM
+        wkbPolyhedralSurfaceM
+        wkbTINM
+        wkbTriangleM
+        wkbPointZM
+        wkbLineStringZM
+        wkbPolygonZM
+        wkbMultiPointZM
+        wkbMultiLineStringZM
+        wkbMultiPolygonZM
+        wkbGeometryCollectionZM
+        wkbCircularStringZM
+        wkbCompoundCurveZM
+        wkbCurvePolygonZM
+        wkbMultiCurveZM
+        wkbMultiSurfaceZM
+        wkbCurveZM
+        wkbSurfaceZM
+        wkbPolyhedralSurfaceZM
+        wkbTINZM
+        wkbTriangleZM
+        wkbPoint25D
+        wkbLineString25D
+        wkbPolygon25D
+        wkbMultiPoint25D
+        wkbMultiLineString25D
+        wkbMultiPolygon25D
+        wkbGeometryCollection25D
+
+    ctypedef enum OGRFieldType:
+        OFTInteger
+        OFTIntegerList
+        OFTReal
+        OFTRealList
+        OFTString
+        OFTStringList
+        OFTWideString
+        OFTWideStringList
+        OFTBinary
+        OFTDate
+        OFTTime
+        OFTDateTime
+        OFTMaxType
+
     char *  OGRGeometryTypeToName(int)
 
     char * ODsCCreateLayer = "CreateLayer"
@@ -79,7 +167,7 @@ cdef extern from "ogr_srs_api.h":
     int     OCTTransform (void *ct, int nCount, double *x, double *y, double *z)
 
 cdef extern from "ogr_api.h":
-    char *  OGR_Dr_GetName (void *driver)
+    const char * OGR_Dr_GetName (void *driver)
     void *  OGR_Dr_CreateDataSource (void *driver, const char *path, char **options)
     int     OGR_Dr_DeleteDataSource (void *driver, char *)
     void *  OGR_Dr_Open (void *driver, const char *path, int bupdate)
@@ -119,7 +207,7 @@ cdef extern from "ogr_api.h":
     void *  OGR_FD_GetFieldDefn (void *featuredefn, int n)
     int     OGR_FD_GetGeomType (void *featuredefn)
     char *  OGR_FD_GetName (void *featuredefn)
-    void *  OGR_Fld_Create (char *name, int fieldtype)
+    void *  OGR_Fld_Create (char *name, OGRFieldType fieldtype)
     void    OGR_Fld_Destroy (void *fielddefn)
     char *  OGR_Fld_GetNameRef (void *fielddefn)
     int     OGR_Fld_GetPrecision (void *fielddefn)
@@ -148,7 +236,7 @@ cdef extern from "ogr_api.h":
     void    OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes)
     int     OGR_G_WkbSize (void *geometry)
     OGRErr  OGR_L_CreateFeature (void *layer, void *feature)
-    int     OGR_L_CreateField (void *layer, void *fielddefn, int flexible)
+    OGRErr  OGR_L_CreateField (void *layer, void *fielddefn, int flexible)
     OGRErr  OGR_L_GetExtent (void *layer, void *extent, int force)
     void *  OGR_L_GetFeature (void *layer, int n)
     int     OGR_L_GetFeatureCount (void *layer, int m)


=====================================
fiona/ogrext2.pxd
=====================================
@@ -9,13 +9,102 @@ cdef extern from "ogr_core.h":
 
     ctypedef int OGRErr
 
+    ctypedef enum OGRwkbGeometryType:
+        wkbUnknown
+        wkbPoint
+        wkbLineString
+        wkbPolygon
+        wkbMultiPoint
+        wkbMultiLineString
+        wkbMultiPolygon
+        wkbGeometryCollection
+        wkbCircularString
+        wkbCompoundCurve
+        wkbCurvePolygon
+        wkbMultiCurve
+        wkbMultiSurface
+        wkbCurve
+        wkbSurface
+        wkbPolyhedralSurface
+        wkbTIN
+        wkbTriangle
+        wkbNone
+        wkbLinearRing
+        wkbCircularStringZ
+        wkbCompoundCurveZ
+        wkbCurvePolygonZ
+        wkbMultiCurveZ
+        wkbMultiSurfaceZ
+        wkbCurveZ
+        wkbSurfaceZ
+        wkbPolyhedralSurfaceZ
+        wkbTINZ
+        wkbTriangleZ
+        wkbPointM
+        wkbLineStringM
+        wkbPolygonM
+        wkbMultiPointM
+        wkbMultiLineStringM
+        wkbMultiPolygonM
+        wkbGeometryCollectionM
+        wkbCircularStringM
+        wkbCompoundCurveM
+        wkbCurvePolygonM
+        wkbMultiCurveM
+        wkbMultiSurfaceM
+        wkbCurveM
+        wkbSurfaceM
+        wkbPolyhedralSurfaceM
+        wkbTINM
+        wkbTriangleM
+        wkbPointZM
+        wkbLineStringZM
+        wkbPolygonZM
+        wkbMultiPointZM
+        wkbMultiLineStringZM
+        wkbMultiPolygonZM
+        wkbGeometryCollectionZM
+        wkbCircularStringZM
+        wkbCompoundCurveZM
+        wkbCurvePolygonZM
+        wkbMultiCurveZM
+        wkbMultiSurfaceZM
+        wkbCurveZM
+        wkbSurfaceZM
+        wkbPolyhedralSurfaceZM
+        wkbTINZM
+        wkbTriangleZM
+        wkbPoint25D
+        wkbLineString25D
+        wkbPolygon25D
+        wkbMultiPoint25D
+        wkbMultiLineString25D
+        wkbMultiPolygon25D
+        wkbGeometryCollection25D
+
+    ctypedef enum OGRFieldType:
+        OFTInteger
+        OFTIntegerList
+        OFTReal
+        OFTRealList
+        OFTString
+        OFTStringList
+        OFTWideString
+        OFTWideStringList
+        OFTBinary
+        OFTDate
+        OFTTime
+        OFTDateTime
+        OFTInteger64
+        OFTInteger64List
+        OFTMaxType
+
     ctypedef int OGRFieldSubType
-    ctypedef enum OGRFieldSubType:
-        OFSTNone = 0
-        OFSTBoolean = 1
-        OFSTInt16 = 2
-        OFSTFloat32 = 3
-        OFSTMaxSubType = 3
+    cdef int OFSTNone = 0
+    cdef int OFSTBoolean = 1
+    cdef int OFSTInt16 = 2
+    cdef int OFSTFloat32 = 3
+    cdef int OFSTMaxSubType = 3
 
     ctypedef struct OGREnvelope:
         double MinX
@@ -35,9 +124,9 @@ cdef extern from "gdal.h":
     void * GDALGetDriverByName(const char * pszName)
     void * GDALOpenEx(const char * pszFilename,
                       unsigned int nOpenFlags,
-                      const char ** papszAllowedDrivers,
-                      const char ** papszOpenOptions,
-                      const char *const *papszSibling1Files
+                      const char *const *papszAllowedDrivers,
+                      const char *const *papszOpenOptions,
+                      const char *const *papszSiblingFiles
                       )
     int GDAL_OF_UPDATE
     int GDAL_OF_READONLY
@@ -147,7 +236,7 @@ cdef extern from "ogr_srs_api.h":
 
 cdef extern from "ogr_api.h":
 
-    char *  OGR_Dr_GetName (void *driver)
+    const char * OGR_Dr_GetName (void *driver)
     void *  OGR_Dr_CreateDataSource (void *driver, const char *path, char **options)
     int     OGR_Dr_DeleteDataSource (void *driver, char *)
     void *  OGR_Dr_Open (void *driver, const char *path, int bupdate)
@@ -178,7 +267,7 @@ cdef extern from "ogr_api.h":
     void *  OGR_FD_GetFieldDefn (void *featuredefn, int n)
     int     OGR_FD_GetGeomType (void *featuredefn)
     char *  OGR_FD_GetName (void *featuredefn)
-    void *  OGR_Fld_Create (char *name, int fieldtype)
+    void *  OGR_Fld_Create (char *name, OGRFieldType fieldtype)
     void    OGR_Fld_Destroy (void *fielddefn)
     char *  OGR_Fld_GetNameRef (void *fielddefn)
     int     OGR_Fld_GetPrecision (void *fielddefn)
@@ -209,7 +298,7 @@ cdef extern from "ogr_api.h":
     void    OGR_G_ImportFromWkb (void *geometry, unsigned char *bytes, int nbytes)
     int     OGR_G_WkbSize (void *geometry)
     OGRErr  OGR_L_CreateFeature (void *layer, void *feature)
-    int     OGR_L_CreateField (void *layer, void *fielddefn, int flexible)
+    OGRErr  OGR_L_CreateField (void *layer, void *fielddefn, int flexible)
     OGRErr  OGR_L_GetExtent (void *layer, void *extent, int force)
     void *  OGR_L_GetFeature (void *layer, int n)
     int     OGR_L_GetFeatureCount (void *layer, int m)


=====================================
setup.py
=====================================
@@ -104,6 +104,7 @@ libraries = []
 extra_link_args = []
 gdal_output = [None for i in range(4)]
 gdalversion = None
+language = None
 
 if 'clean' not in sys.argv:
     try:
@@ -172,6 +173,11 @@ if 'clean' not in sys.argv:
             log.info("Copying proj data from %s" % projdatadir)
             copy_data_tree(projdatadir, 'fiona/proj_data')
 
+    if "--cython-language" in sys.argv:
+        index = sys.argv.index("--cython-language")
+        sys.argv.pop(index)
+        language = sys.argv.pop(index).lower()
+
     gdal_version_parts = gdalversion.split('.')
     gdal_major_version = int(gdal_version_parts[0])
     gdal_minor_version = int(gdal_version_parts[1])
@@ -182,11 +188,15 @@ ext_options = dict(
     libraries=libraries,
     extra_link_args=extra_link_args)
 
-ext_options_cpp = ext_options.copy()
 # GDAL 2.3+ requires C++11
-if sys.platform == "win32":
-    ext_options_cpp["extra_compile_args"] = ["/std:c++11"]
-else:
+
+if language == "c++":
+    ext_options["language"] = "c++"
+    if sys.platform != "win32":
+        ext_options["extra_compile_args"] = ["-std=c++11"]
+
+ext_options_cpp = ext_options.copy()
+if sys.platform != "win32":
     ext_options_cpp["extra_compile_args"] = ["-std=c++11"]
 
 


=====================================
tests/conftest.py
=====================================
@@ -1,9 +1,11 @@
 """pytest fixtures and automatic test data generation."""
+
+import copy
 import json
 import os.path
+import shutil
 import tarfile
 import zipfile
-import copy
 
 from click.testing import CliRunner
 import pytest
@@ -54,6 +56,14 @@ def data_dir():
     return os.path.abspath(os.path.join(os.path.dirname(__file__), 'data'))
 
 
+ at pytest.fixture(scope='function')
+def data(tmpdir, data_dir):
+    """A temporary directory containing a copy of the files in data."""
+    for filename in _COUTWILDRNP_FILES:
+        shutil.copy(os.path.join(data_dir, filename), str(tmpdir))
+    return tmpdir
+
+
 @pytest.fixture(scope='session')
 def path_curves_line_csv(data_dir):
     """Path to ```curves_line.csv``"""
@@ -250,4 +260,4 @@ def unittest_data_dir(data_dir, request):
 @pytest.fixture(scope="class")
 def unittest_path_coutwildrnp_shp(path_coutwildrnp_shp, request):
     """Makes shapefile path available to unittest tests"""
-    request.cls.path_coutwildrnp_shp = path_coutwildrnp_shp
\ No newline at end of file
+    request.cls.path_coutwildrnp_shp = path_coutwildrnp_shp


=====================================
tests/test__env.py
=====================================
@@ -0,0 +1,125 @@
+"""Tests of _env util module"""
+
+import pytest
+
+from fiona._env import GDALDataFinder, PROJDataFinder
+
+from .conftest import gdal_version
+
+
+ at pytest.fixture
+def mock_wheel(tmpdir):
+    """A fake rasterio wheel"""
+    moduledir = tmpdir.mkdir("rasterio")
+    moduledir.ensure("__init__.py")
+    moduledir.ensure("_env.py")
+    moduledir.ensure("gdal_data/pcs.csv")
+    moduledir.ensure("proj_data/epsg")
+    return moduledir
+
+
+ at pytest.fixture
+def mock_fhs(tmpdir):
+    """A fake FHS system"""
+    tmpdir.ensure("share/gdal/pcs.csv")
+    tmpdir.ensure("share/proj/epsg")
+    return tmpdir
+
+
+ at pytest.fixture
+def mock_debian(tmpdir):
+    """A fake Debian multi-install system"""
+    tmpdir.ensure("share/gdal/1.11/pcs.csv")
+    tmpdir.ensure("share/gdal/2.0/pcs.csv")
+    tmpdir.ensure("share/gdal/2.1/pcs.csv")
+    tmpdir.ensure("share/gdal/2.2/pcs.csv")
+    tmpdir.ensure("share/gdal/2.3/pcs.csv")
+    tmpdir.ensure("share/gdal/2.4/pcs.csv")
+    tmpdir.ensure("share/proj/epsg")
+    return tmpdir
+
+
+def test_search_wheel_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a non-wheel"""
+    finder = GDALDataFinder()
+    assert not finder.search_wheel(str(tmpdir))
+
+
+def test_search_wheel_gdal_data(mock_wheel):
+    """Find GDAL data in a wheel"""
+    finder = GDALDataFinder()
+    assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data"))
+
+
+def test_search_prefix_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus prefix"""
+    finder = GDALDataFinder()
+    assert not finder.search_prefix(str(tmpdir))
+
+
+def test_search_prefix_gdal_data(mock_fhs):
+    """Find GDAL data under prefix"""
+    finder = GDALDataFinder()
+    assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share").join("gdal"))
+
+
+def test_search_debian_gdal_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus Debian location"""
+    finder = GDALDataFinder()
+    assert not finder.search_debian(str(tmpdir))
+
+
+def test_search_debian_gdal_data(mock_debian):
+    """Find GDAL data under Debian locations"""
+    finder = GDALDataFinder()
+    assert finder.search_debian(str(mock_debian)) == str(mock_debian.join("share").join("gdal").join("{}.{}".format(gdal_version.major, gdal_version.minor)))
+
+
+def test_search_gdal_data_wheel(mock_wheel):
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("gdal_data"))
+
+
+def test_search_gdal_data_fhs(mock_fhs):
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share").join("gdal"))
+
+
+def test_search_gdal_data_debian(mock_debian):
+    """Find GDAL data under Debian locations"""
+    finder = GDALDataFinder()
+    assert finder.search(str(mock_debian)) == str(mock_debian.join("share").join("gdal").join("{}.{}".format(gdal_version.major, gdal_version.minor)))
+
+
+def test_search_wheel_proj_data_failure(tmpdir):
+    """Fail to find GDAL data in a non-wheel"""
+    finder = PROJDataFinder()
+    assert not finder.search_wheel(str(tmpdir))
+
+
+def test_search_wheel_proj_data(mock_wheel):
+    """Find GDAL data in a wheel"""
+    finder = PROJDataFinder()
+    assert finder.search_wheel(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data"))
+
+
+def test_search_prefix_proj_data_failure(tmpdir):
+    """Fail to find GDAL data in a bogus prefix"""
+    finder = PROJDataFinder()
+    assert not finder.search_prefix(str(tmpdir))
+
+
+def test_search_prefix_proj_data(mock_fhs):
+    """Find GDAL data under prefix"""
+    finder = PROJDataFinder()
+    assert finder.search_prefix(str(mock_fhs)) == str(mock_fhs.join("share").join("proj"))
+
+
+def test_search_proj_data_wheel(mock_wheel):
+    finder = PROJDataFinder()
+    assert finder.search(str(mock_wheel.join("_env.py"))) == str(mock_wheel.join("proj_data"))
+
+
+def test_search_proj_data_fhs(mock_fhs):
+    finder = PROJDataFinder()
+    assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share").join("proj"))


=====================================
tests/test_bigint.py
=====================================
@@ -18,7 +18,7 @@ of 64 bit integer, OFTReal is chosen)
 import pytest
 
 import fiona
-from fiona.ogrext import calc_gdal_version_num, get_gdal_version_num
+from fiona.env import calc_gdal_version_num, get_gdal_version_num
 
 
 @pytest.mark.xfail(fiona.gdal_version.major < 2,


=====================================
tests/test_collection.py
=====================================
@@ -206,6 +206,7 @@ class TestReading(object):
         with fiona.open(path_coutwildrnp_shp, "r") as c:
             assert c.name == 'coutwildrnp'
             assert len(c) == 67
+            assert c.crs
         assert c.closed
 
     def test_iter_one(self):
@@ -866,3 +867,10 @@ def test_collection_zip_http():
     ds = fiona.Collection('http://raw.githubusercontent.com/OSGeo/gdal/master/autotest/ogr/data/poly.zip', vsi='zip+http')
     assert ds.path == '/vsizip/vsicurl/http://raw.githubusercontent.com/OSGeo/gdal/master/autotest/ogr/data/poly.zip'
     assert len(ds) == 10
+
+
+def test_encoding_option_warning(tmpdir, caplog):
+    """There is no ENCODING creation option log warning for GeoJSON"""
+    ds = fiona.Collection(str(tmpdir.join("test.geojson")), "w", driver="GeoJSON", crs="epsg:4326",
+            schema={"geometry": "Point", "properties": {"foo": "int"}})
+    assert not caplog.text


=====================================
tests/test_compound_crs.py
=====================================
@@ -0,0 +1,11 @@
+"""Test of compound CRS crash avoidance"""
+
+import fiona
+
+
+def test_compound_crs(data):
+    """Don't crash"""
+    prj = data.join("coutwildrnp.prj")
+    prj.write("""COMPD_CS["unknown",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AUTHORITY["EPSG","4326"]],VERT_CS["unknown",VERT_DATUM["unknown",2005],UNIT["metre",1.0,AUTHORITY["EPSG","9001"]],AXIS["Up",UP]]]""")
+    with fiona.open(str(data.join("coutwildrnp.shp"))) as collection:
+        assert collection.crs == {}


=====================================
tests/test_datetime.py
=====================================
@@ -50,10 +50,10 @@ class TestDateFieldSupport:
                 }
             },
         ]
-        with fiona.open(path, "w", driver=driver, schema=schema) as collection:
+        with fiona.Env(), fiona.open(path, "w", driver=driver, schema=schema) as collection:
             collection.writerecords(records)
-            
-        with fiona.open(path, "r") as collection:
+
+        with fiona.Env(), fiona.open(path, "r") as collection:
             schema = collection.schema
             features = list(collection)
 
@@ -64,7 +64,7 @@ class TestDateFieldSupport:
     def test_shapefile(self):
         driver = "ESRI Shapefile"
         schema, features = self.write_data(driver)
-        
+
         assert schema["properties"]["date"] == "date"
         assert features[0]["properties"]["date"] == DATE_EXAMPLE
         assert features[1]["properties"]["date"] is None
@@ -73,7 +73,7 @@ class TestDateFieldSupport:
     def test_gpkg(self):
         driver = "GPKG"
         schema, features = self.write_data(driver)
-        
+
         assert schema["properties"]["date"] == "date"
         assert features[0]["properties"]["date"] == DATE_EXAMPLE
         assert features[1]["properties"]["date"] is None
@@ -83,7 +83,7 @@ class TestDateFieldSupport:
         # GDAL 1: date string format uses / instead of -
         driver = "GeoJSON"
         schema, features = self.write_data(driver)
-        
+
         if GDAL_MAJOR_VER >= 2:
             assert schema["properties"]["date"] == "date"
             assert features[0]["properties"]["date"] == DATE_EXAMPLE
@@ -95,7 +95,7 @@ class TestDateFieldSupport:
     def test_mapinfo(self):
         driver = "MapInfo File"
         schema, features = self.write_data(driver)
-        
+
         assert schema["properties"]["date"] == "date"
         assert features[0]["properties"]["date"] == DATE_EXAMPLE
         assert features[1]["properties"]["date"] is None
@@ -126,10 +126,10 @@ class TestDatetimeFieldSupport:
                 }
             },
         ]
-        with fiona.open(path, "w", driver=driver, schema=schema) as collection:
+        with fiona.Env(), fiona.open(path, "w", driver=driver, schema=schema) as collection:
             collection.writerecords(records)
-            
-        with fiona.open(path, "r") as collection:
+
+        with fiona.Env(), fiona.open(path, "r") as collection:
             schema = collection.schema
             features = list(collection)
 
@@ -143,7 +143,7 @@ class TestDatetimeFieldSupport:
 
         with pytest.raises(DriverSupportError):
             schema, features = self.write_data(driver)
-        
+
         # assert schema["properties"]["datetime"] == "date"
         # assert features[0]["properties"]["datetime"] == "2018-03-25"
         # assert features[1]["properties"]["datetime"] is None
@@ -152,7 +152,7 @@ class TestDatetimeFieldSupport:
     def test_gpkg(self):
         # GDAL 1: datetime silently downgraded to date
         driver = "GPKG"
-        
+
         if GDAL_MAJOR_VER >= 2:
             schema, features = self.write_data(driver)
             assert schema["properties"]["datetime"] == "datetime"
@@ -167,7 +167,7 @@ class TestDatetimeFieldSupport:
         # GDAL 1: date string format uses / instead of -
         driver = "GeoJSON"
         schema, features = self.write_data(driver)
-        
+
         if GDAL_MAJOR_VER >= 2:
             assert schema["properties"]["datetime"] == "datetime"
             assert features[0]["properties"]["datetime"] == DATETIME_EXAMPLE
@@ -179,7 +179,7 @@ class TestDatetimeFieldSupport:
     def test_mapinfo(self):
         driver = "MapInfo File"
         schema, features = self.write_data(driver)
-        
+
         assert schema["properties"]["datetime"] == "datetime"
         assert features[0]["properties"]["datetime"] == DATETIME_EXAMPLE
         assert features[1]["properties"]["datetime"] is None
@@ -210,10 +210,10 @@ class TestTimeFieldSupport:
                 }
             },
         ]
-        with fiona.open(path, "w", driver=driver, schema=schema) as collection:
+        with fiona.Env(), fiona.open(path, "w", driver=driver, schema=schema) as collection:
             collection.writerecords(records)
-            
-        with fiona.open(path, "r") as collection:
+
+        with fiona.Env(), fiona.open(path, "r") as collection:
             schema = collection.schema
             features = list(collection)
 
@@ -235,7 +235,7 @@ class TestTimeFieldSupport:
 
         with pytest.raises(DriverSupportError):
             schema, features = self.write_data(driver)
-    
+
         # if GDAL_MAJOR_VER >= 2:
         #     assert schema["properties"]["time"] == "str"
         #     assert features[0]["properties"]["time"] == TIME_EXAMPLE
@@ -247,7 +247,7 @@ class TestTimeFieldSupport:
         # GDAL 1: time field silently converted to string
         driver = "GeoJSON"
         schema, features = self.write_data(driver)
-    
+
         if GDAL_MAJOR_VER >= 2:
             assert schema["properties"]["time"] == "time"
         else:
@@ -259,7 +259,7 @@ class TestTimeFieldSupport:
         # GDAL 2: null time is converted to 00:00:00 (regression?)
         driver = "MapInfo File"
         schema, features = self.write_data(driver)
-    
+
         assert schema["properties"]["time"] == "time"
         assert features[0]["properties"]["time"] == TIME_EXAMPLE
         if GDAL_MAJOR_VER >= 2:


=====================================
tests/test_env.py
=====================================
@@ -1,16 +1,20 @@
 """Tests of fiona.env"""
 
+import os
+import sys
+
 import pytest
 
 import fiona
-import fiona.env
+from fiona import _env
+from fiona.env import getenv, ensure_env, ensure_env_with_credentials
 from fiona.session import AWSSession
 
 
 def test_nested_credentials(monkeypatch):
     """Check that rasterio.open() doesn't wipe out surrounding credentials"""
 
-    @fiona.env.ensure_env_with_credentials
+    @ensure_env_with_credentials
     def fake_opener(path):
         return fiona.env.getenv()
 
@@ -23,3 +27,65 @@ def test_nested_credentials(monkeypatch):
         gdalenv = fake_opener('s3://foo/bar')
         assert gdalenv['AWS_ACCESS_KEY_ID'] == 'foo'
         assert gdalenv['AWS_SECRET_ACCESS_KEY'] == 'bar'
+
+
+def test_ensure_env_decorator(gdalenv):
+    @ensure_env
+    def f():
+        return getenv()['RASTERIO_ENV']
+    assert f() is True
+
+
+def test_ensure_env_decorator_sets_gdal_data(gdalenv, monkeypatch):
+    """fiona.env.ensure_env finds GDAL from environment"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    monkeypatch.setenv('GDAL_DATA', '/lol/wut')
+    assert f() == '/lol/wut'
+
+
+def test_ensure_env_decorator_sets_gdal_data_prefix(gdalenv, monkeypatch, tmpdir):
+    """fiona.env.ensure_env finds GDAL data under a prefix"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    tmpdir.ensure("share/gdal/pcs.csv")
+    monkeypatch.delenv('GDAL_DATA', raising=False)
+    monkeypatch.setattr(_env, '__file__', str(tmpdir.join("fake.py")))
+    monkeypatch.setattr(sys, 'prefix', str(tmpdir))
+
+    assert f() == str(tmpdir.join("share").join("gdal"))
+
+
+def test_ensure_env_decorator_sets_gdal_data_wheel(gdalenv, monkeypatch, tmpdir):
+    """fiona.env.ensure_env finds GDAL data in a wheel"""
+    @ensure_env
+    def f():
+        return getenv()['GDAL_DATA']
+
+    tmpdir.ensure("gdal_data/pcs.csv")
+    monkeypatch.delenv('GDAL_DATA', raising=False)
+    monkeypatch.setattr(_env, '__file__', str(tmpdir.join(os.path.basename(_env.__file__))))
+
+    assert f() == str(tmpdir.join("gdal_data"))
+
+
+def test_ensure_env_with_decorator_sets_gdal_data_wheel(gdalenv, monkeypatch, tmpdir):
+    """fiona.env.ensure_env finds GDAL data in a wheel"""
+    @ensure_env_with_credentials
+    def f(*args):
+        return getenv()['GDAL_DATA']
+
+    tmpdir.ensure("gdal_data/pcs.csv")
+    monkeypatch.delenv('GDAL_DATA', raising=False)
+    monkeypatch.setattr(_env, '__file__', str(tmpdir.join(os.path.basename(_env.__file__))))
+
+    assert f("foo") == str(tmpdir.join("gdal_data"))
+
+
+def test_ensure_env_crs(path_coutwildrnp_shp):
+    """Decoration of .crs works"""
+    assert fiona.open(path_coutwildrnp_shp).crs


=====================================
tests/test_schema.py
=====================================
@@ -6,7 +6,7 @@ import pytest
 import fiona
 from fiona.errors import SchemaError, UnsupportedGeometryTypeError
 from fiona.schema import FIELD_TYPES, normalize_field_type
-from fiona.ogrext import calc_gdal_version_num, get_gdal_version_num
+from fiona.env import calc_gdal_version_num, get_gdal_version_num
 
 
 def test_schema_ordering_items(tmpdir):


=====================================
tests/test_unicode.py
=====================================
@@ -5,10 +5,12 @@ import os
 import shutil
 import sys
 import tempfile
+from collections import OrderedDict
 
 import pytest
 
 import fiona
+from fiona.errors import SchemaError
 
 
 class TestUnicodePath(object):
@@ -116,3 +118,34 @@ class TestUnicodeStringField(object):
             f = next(iter(c))
             assert f['properties']['label'] == u'徐汇区'
             assert f['properties']['num'] == 0
+
+    @pytest.mark.skipif(sys.platform == 'win32', reason="GDAL binary used on AppVeyor does not have a working libiconv")
+    def test_gb2312_field_wrong_encoding(self):
+        """Attempt to create field with a name not supported by the encoding
+
+        ESRI Shapefile driver defaults to ISO-8859-1 encoding if none is
+        specified. This doesn't support the field name used. Previously this
+        went undetected and would raise a KeyError later when the user tried
+        to write a feature to the layer. Instead we raise a more useful error.
+
+        See GH#595.
+        """
+        field_name = u"区县名称"
+        meta = {
+            "schema": {
+                "properties": OrderedDict([(field_name, "int")]),
+                "geometry": "Point",
+            },
+            "driver": "ESRI Shapefile",
+        }
+        feature = {
+            "properties": {field_name: 123},
+            "geometry": {"type": "Point", "coordinates": [1, 2]}
+        }
+        # when encoding is specified, write is successful
+        with fiona.open(os.path.join(self.tempdir, "test1.shp"), "w", encoding="GB2312", **meta) as collection:
+            collection.write(feature)
+        # no encoding
+        with pytest.raises(SchemaError):
+            fiona.open(os.path.join(self.tempdir, "test2.shp"), "w", **meta)
+



View it on GitLab: https://salsa.debian.org/debian-gis-team/fiona/commit/652d6524a2d2f22a232e856626e16abfa30351f3

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/fiona/commit/652d6524a2d2f22a232e856626e16abfa30351f3
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20181116/f93c3807/attachment-0001.html>


More information about the Pkg-grass-devel mailing list