[Git][debian-gis-team/fiona][upstream] New upstream version 1.9~b2

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Sun Jan 22 19:31:19 GMT 2023



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


Commits:
216cbde5 by Bas Couwenberg at 2023-01-22T19:52:19+01:00
New upstream version 1.9~b2
- - - - -


26 changed files:

- CHANGES.txt
- README.rst
- docs/index.rst
- + docs/install.rst
- docs/manual.rst
- fiona/__init__.py
- fiona/_geometry.pyx
- fiona/fio/bounds.py
- fiona/fio/calc.py
- fiona/fio/cat.py
- fiona/fio/collect.py
- fiona/fio/distrib.py
- fiona/fio/dump.py
- fiona/fio/filter.py
- fiona/fio/info.py
- fiona/fio/insp.py
- fiona/fio/load.py
- fiona/model.py
- fiona/ogrext.pyx
- fiona/transform.py
- tests/test_collection.py
- tests/test_feature.py
- tests/test_fio_calc.py
- tests/test_geometry.py
- tests/test_model.py
- tests/test_transform.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -3,6 +3,24 @@ Changes
 
 All issue numbers are relative to https://github.com/Toblerity/Fiona/issues.
 
+1.9b2 (2023-01-22)
+------------------
+
+- Add Feature.__geo_interface__ property (#1181).
+- Invalid creation options are filtered and ignored (#1180).
+- The readme doc has been shortened and freshened up, with a modern example for
+  version 1.9.0 (#1174).
+- The Geometry class now provides and looks for __geo_interface__ (#1174).
+- The top level fiona module now exports Feature, Geometry, and Properties
+  (#1174).
+- Functions that take Feature or Geometry objects will continue to take dicts
+  or objects that provide __geo_interface__ (#1177). This reverses the
+  deprecation introduced in 1.9a2.
+- Python ignores SIGPIPE by default. By never catching BrokenPipeError via
+  `except Exception` when, for example, piping the output of rio-shapes to
+  the Unix head program, we avoid getting an unhandled BrokenPipeError message
+  when the interpreter shuts down (#2689).
+
 1.9b1 (2022-12-13)
 ------------------
 


=====================================
README.rst
=====================================
@@ -2,339 +2,115 @@
 Fiona
 =====
 
-Fiona is GDAL_'s neat and nimble vector API for Python programmers.
-
 .. image:: https://github.com/Toblerity/Fiona/workflows/Linux%20CI/badge.svg?branch=maint-1.9
    :target: https://github.com/Toblerity/Fiona/actions?query=branch%3Amaint-1.9
 
-.. image:: https://ci.appveyor.com/api/projects/status/github/Toblerity/Fiona?svg=true
-   :target: https://ci.appveyor.com/project/sgillies/fiona/branch/maint-1.9
-
-.. image:: https://coveralls.io/repos/Toblerity/Fiona/badge.svg
-   :target: https://coveralls.io/r/Toblerity/Fiona
+Fiona streams simple feature data to and from GIS formats like GeoPackage and
+Shapefile.
 
-Fiona is designed to be simple and dependable. It focuses on reading and
-writing data in standard Python IO style and relies upon familiar Python types
-and protocols such as files, dictionaries, mappings, and iterators instead of
-classes specific to OGR. Fiona can read and write real-world data using
-multi-layered GIS formats and zipped virtual file systems and integrates
-readily with other Python GIS packages such as pyproj_, Rtree_, and Shapely_.
+Fiona can read and write real-world data using multi-layered GIS formats,
+zipped and in-memory virtual file systems, from files on your hard drive or in
+cloud storage. This project includes Python modules and a command line
+interface (CLI).
 
-Fiona is supported only on CPython versions 3.6+.
+Fiona depends on `GDAL <https://gdal.org>`__ but is different from GDAL's own
+`bindings <https://gdal.org/api/python_bindings.html>`__. Fiona is designed to
+be highly productive and to make it easy to write code which is easy to read.
 
-Why the name "Fiona"? Because Fiona Is OGR's Neat and Nimble API for Python programmers. And a Shrek reference made us laugh.
+Installation
+============
 
-For more details, see:
+Fiona has several `extension modules
+<https://docs.python.org/3/extending/extending.html>`__ which link against
+libgdal. This complicates installation. Binary distributions (wheels)
+containing libgdal and its own dependencies are available from the Python
+Package Index and can be installed using pip.
 
-* Fiona `home page <https://github.com/Toblerity/Fiona>`__
-* `Docs and manual <https://fiona.readthedocs.io/>`__
-* `Examples <https://github.com/Toblerity/Fiona/tree/master/examples>`__
-* Main `user discussion group <https://fiona.groups.io/g/main>`__
-* `Developers discussion group <https://fiona.groups.io/g/dev>`__
+.. code-block:: console
 
-Usage
-=====
+    pip install fiona
 
-Collections
------------
+These wheels are mainly intended to make installation easy for simple
+applications, not so much for production. They are not tested for compatibility
+with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's
+optional format drivers. If you need, for example, GML support you will need to
+build and install Fiona from a source distribution.
 
-Records are read from and written to ``file``-like `Collection` objects
-returned from the ``fiona.open()`` function.  Records are mappings modeled on
-the GeoJSON format. They don't have any spatial methods of their own, so if you
-want to do anything fancy with them you will probably need Shapely or something
-like it. Here is an example of using Fiona to read some records from one data
-file, change their geometry attributes, and write them to a new data file.
+Many users find Anaconda and conda-forge a good way to install Fiona and get
+access to more optional format drivers (like GML).
 
-.. code-block:: python
+Fiona 1.9 (coming soon) requires Python 3.7 or higher and GDAL 3.2 or higher.
 
-    import fiona
+Python Usage
+============
 
-    # Open a file for reading. We'll call this the "source."
+Features are read from and written to file-like ``Collection`` objects
+returned from the ``fiona.open()`` function. Features are data classes modeled
+on the GeoJSON format. They don't have any spatial methods of their own, so if
+you want to do anything fancy with them you will need Shapely or something like
+it. Here is an example of using Fiona to read some features from one data file,
+change their geometry attributes using Shapely, and write them to a new data
+file.
 
-    with fiona.open('tests/data/coutwildrnp.shp') as src:
+.. code-block:: python
 
-        # The file we'll write to, the "destination", must be initialized
-        # with a coordinate system, a format driver name, and
-        # a record schema.  We can get initial values from the open
-        # collection's ``meta`` property and then modify them as
-        # desired.
+    import fiona
+    from fiona import Feature, Geometry
+    from shapely.geometry import mapping, shape
 
-        meta = src.meta
-        meta['schema']['geometry'] = 'Point'
+    # Open a file for reading. We'll call this the source.
+    with fiona.open("tests/data/coutwildrnp.shp") as src:
 
-        # Open an output file, using the same format driver and
-        # coordinate reference system as the source. The ``meta``
-        # mapping fills in the keyword parameters of fiona.open().
+        # The file we'll write to must be initialized with a coordinate
+        # system, a format driver name, and a record schema. We can get
+        # initial values from the open source's profile property and then
+        # modify them as we need.
+        profile = src.profile
+        profile["schema"]["geometry"] = "Point"
+        profile["driver"] = "GPKG"
 
-        with fiona.open('test_write.shp', 'w', **meta) as dst:
+        # Open an output file, using the same format driver and coordinate
+        # reference system as the source. The profile mapping fills in the
+        # keyword parameters of fiona.open.
+        with fiona.open("/tmp/example.gpkg", "w", **profile) as dst:
 
             # Process only the records intersecting a box.
             for f in src.filter(bbox=(-107.0, 37.0, -105.0, 39.0)):
 
-                # Get a point on the boundary of the record's
-                # geometry.
-
-                f['geometry'] = {
-                    'type': 'Point',
-                    'coordinates': f['geometry']['coordinates'][0][0]}
-
-                # Write the record out.
-
-                dst.write(f)
-
-    # The destination's contents are flushed to disk and the file is
-    # closed when its ``with`` block ends. This effectively
-    # executes ``dst.flush(); dst.close()``.
-
-Reading Multilayer data
------------------------
-
-Collections can also be made from single layers within multilayer files or
-directories of data. The target layer is specified by name or by its integer
-index within the file or directory. The ``fiona.listlayers()`` function
-provides an index ordered list of layer names.
-
-.. code-block:: python
+                # Get the feature's centroid.
+                centroid_shp = shape(f.geometry).centroid
+                new_geom = Geometry.from_dict(centroid_shp)
 
-    for layername in fiona.listlayers('tests/data'):
-        with fiona.open('tests/data', layer=layername) as src:
-            print(layername, len(src))
-
-    # Output:
-    # ('coutwildrnp', 67)
-
-Layer can also be specified by index. In this case, ``layer=0`` and
-``layer='test_uk'`` specify the same layer in the data file or directory.
-
-.. code-block:: python
-
-    for i, layername in enumerate(fiona.listlayers('tests/data')):
-        with fiona.open('tests/data', layer=i) as src:
-            print(i, layername, len(src))
-
-    # Output:
-    # (0, 'coutwildrnp', 67)
-
-Writing Multilayer data
------------------------
-
-Multilayer data can be written as well. Layers must be specified by name when
-writing.
-
-.. code-block:: python
-
-    with open('tests/data/cowildrnp.shp') as src:
-        meta = src.meta
-        f = next(src)
-
-    with fiona.open('/tmp/foo', 'w', layer='bar', **meta) as dst:
-        dst.write(f)
-
-    print(fiona.listlayers('/tmp/foo'))
-
-    with fiona.open('/tmp/foo', layer='bar') as src:
-        print(len(src))
-        f = next(src)
-        print(f['geometry']['type'])
-        print(f['properties'])
-
-        # Output:
-        # ['bar']
-        # 1
-        # Polygon
-        # OrderedDict([('PERIMETER', 1.22107), ('FEATURE2', None), ('NAME', 'Mount Naomi Wilderness'), ('FEATURE1', 'Wilderness'), ('URL', 'http://www.wilderness.net/index.cfm?fuse=NWPS&sec=wildView&wname=Mount%20Naomi'), ('AGBUR', 'FS'), ('AREA', 0.0179264), ('STATE_FIPS', '49'), ('WILDRNP020', 332), ('STATE', 'UT')])
-
-A view of the /tmp/foo directory will confirm the creation of the new files.
-
-.. code-block:: console
-
-    $ ls /tmp/foo
-    bar.cpg bar.dbf bar.prj bar.shp bar.shx
-
-Collections from archives and virtual file systems
---------------------------------------------------
-
-Zip and Tar archives can be treated as virtual filesystems and Collections can
-be made from paths and layers within them. In other words, Fiona lets you read
-and write zipped Shapefiles.
-
-.. code-block:: python
+                # Write the feature out.
+                dst.write(
+                    Feature(geometry=new_geom, properties=f.properties)
+                )
 
-    for i, layername in enumerate(fiona.listlayers('zip://tests/data/coutwildrnp.zip')):
-        with fiona.open('zip://tests/data/coutwildrnp.zip', layer=i) as src:
-            print(i, layername, len(src))
+        # The destination's contents are flushed to disk and the file is
+        # closed when its with block ends. This effectively
+        # executes ``dst.flush(); dst.close()``.
 
-    # Output:
-    # (0, 'coutwildrnp', 67)
-
-Fiona can also read from more exotic file systems. For instance, a
-zipped shape file in S3 can be accessed like so:
-
-.. code-block:: python
-
-   with fiona.open('zip+s3://mapbox/rasterio/coutwildrnp.zip') as src:
-       print(len(src))
-
-   # Output:
-   # 67
-
-
-Fiona CLI
+CLI Usage
 =========
 
 Fiona's command line interface, named "fio", is documented at `docs/cli.rst
-<https://github.com/Toblerity/Fiona/blob/master/docs/cli.rst>`__. Its ``fio
-info`` pretty prints information about a data file.
-
-.. code-block:: console
-
-    $ fio info --indent 2 tests/data/coutwildrnp.shp
-    {
-      "count": 67,
-      "crs": "EPSG:4326",
-      "driver": "ESRI Shapefile",
-      "bounds": [
-        -113.56424713134766,
-        37.0689811706543,
-        -104.97087097167969,
-        41.99627685546875
-      ],
-      "schema": {
-        "geometry": "Polygon",
-        "properties": {
-          "PERIMETER": "float:24.15",
-          "FEATURE2": "str:80",
-          "NAME": "str:80",
-          "FEATURE1": "str:80",
-          "URL": "str:101",
-          "AGBUR": "str:80",
-          "AREA": "float:24.15",
-          "STATE_FIPS": "str:80",
-          "WILDRNP020": "int:10",
-          "STATE": "str:80"
-        }
-      }
-    }
-
-Installation
-============
-
-Fiona requires Python 3.6+ and GDAL/OGR 1.8+. To build from
-a source distribution you will need a C compiler and GDAL and Python
-development headers and libraries (libgdal1-dev for Debian/Ubuntu, gdal-dev for
-CentOS/Fedora).
-
-To build from a repository copy, you will also need Cython to build C sources
-from the project's .pyx files. See the project's requirements-dev.txt file for
-guidance.
-
-The `Kyngchaos GDAL frameworks
-<https://www.kyngchaos.com/software/frameworks/#gdal_complete>`__ will satisfy
-the GDAL/OGR dependency for OS X, as will Homebrew's GDAL Formula (``brew install
-gdal``).
-
-Python Requirements
--------------------
-
-Fiona depends on the modules ``cligj`` and ``munch``.
-Pip will fetch these requirements for you, but users installing Fiona from a
-Windows installer must get them separately.
-
-Unix-like systems
------------------
-
-Assuming you're using a virtualenv (if not, skip to the 4th command) and
-GDAL/OGR libraries, headers, and `gdal-config`_ program are installed to well
-known locations on your system via your system's package manager (``brew
-install gdal`` using Homebrew on OS X), installation is this simple.
-
-.. code-block:: console
-
-  $ mkdir fiona_env
-  $ virtualenv fiona_env
-  $ source fiona_env/bin/activate
-  (fiona_env)$ pip install fiona
-
-If gdal-config is not available or if GDAL/OGR headers and libs aren't
-installed to a well known location, you must set include dirs, library dirs,
-and libraries options via the setup.cfg file or setup command line as shown
-below (using ``git``). You must also specify the version of the GDAL API on the
-command line using the ``--gdalversion`` argument (see example below) or with
-the ``GDAL_VERSION`` environment variable (e.g. ``export GDAL_VERSION=2.1``).
-
-.. code-block:: console
-
-  (fiona_env)$ git clone git://github.com/Toblerity/Fiona.git
-  (fiona_env)$ cd Fiona
-  (fiona_env)$ python setup.py build_ext -I/path/to/gdal/include -L/path/to/gdal/lib -lgdal install --gdalversion 2.1
-
-Or specify that build options and GDAL API version should be provided by a
-particular gdal-config program.
-
-.. code-block:: console
-
-  (fiona_env)$ GDAL_CONFIG=/path/to/gdal-config pip install fiona
-
-Windows
--------
-
-Binary installers are available at
-https://www.lfd.uci.edu/~gohlke/pythonlibs/#fiona and coming eventually to PyPI.
-
-You can download a binary distribution of GDAL from `here
-<https://www.gisinternals.com/release.php>`_.  You will also need to download
-the compiled libraries and headers (include files).
-
-When building from source on Windows, it is important to know that setup.py
-cannot rely on gdal-config, which is only present on UNIX systems, to discover
-the locations of header files and libraries that Fiona needs to compile its
-C extensions. On Windows, these paths need to be provided by the user.
-You will need to find the include files and the library files for gdal and
-use setup.py as follows. You must also specify the version of the GDAL API on the
-command line using the ``--gdalversion`` argument (see example below) or with
-the ``GDAL_VERSION`` environment variable (e.g. ``set GDAL_VERSION=2.1``).
+<https://github.com/Toblerity/Fiona/blob/master/docs/cli.rst>`__. The CLI has a
+number of different commands. Its ``fio cat`` command streams GeoJSON features
+from any dataset.
 
 .. code-block:: console
 
-    $ python setup.py build_ext -I<path to gdal include files> -lgdal_i -L<path to gdal library> install --gdalversion 2.1
+    $ fio cat --compact tests/data/coutwildrnp.shp | jq -c '.'
+    {"geometry":{"coordinates":[[[-111.73527526855469,41.995094299316406],...]]}}
+    ...
 
-Note: The following environment variables needs to be set so that Fiona works correctly:
+Documentation
+=============
 
-* The directory containing the GDAL DLL (``gdal304.dll`` or similar) needs to be in your
-  Windows ``PATH`` (e.g. ``C:\gdal\bin``).
-* The gdal-data directory needs to be in your Windows ``PATH`` or the environment variable
-  ``GDAL_DATA`` must be set (e.g. ``C:\gdal\bin\gdal-data``).
-* The environment variable ``PROJ_LIB`` (PROJ < 9.1) | ``PROJ_DATA`` (PROJ 9.1+) must be set to the proj data directory (e.g.
-  ``C:\gdal\bin\proj6\share``)
+For more details about this project, please see:
 
-The `Appveyor CI build <https://ci.appveyor.com/project/sgillies/fiona/history>`__
-uses the GISInternals GDAL binaries to build Fiona. This produces a binary wheel
-for successful builds, which includes GDAL and other dependencies, for users
-wanting to try an unstable development version.
-The `Appveyor configuration file <https://github.com/Toblerity/Fiona/blob/master/appveyor.yml>`__ may be a useful example for
-users building from source on Windows.
-
-Development and testing
-=======================
-
-Building from the source requires Cython. Tests require `pytest <https://pytest.org>`_. If the GDAL/OGR
-libraries, headers, and `gdal-config`_ program are installed to well known
-locations on your system (via your system's package manager), you can do this::
-
-  (fiona_env)$ git clone git://github.com/Toblerity/Fiona.git
-  (fiona_env)$ cd Fiona
-  (fiona_env)$ pip install cython
-  (fiona_env)$ pip install -e .[test]
-  (fiona_env)$ pytest
-
-If you have a non-standard environment, you'll need to specify the include and
-lib dirs and GDAL library on the command line::
-
-  (fiona_env)$ python setup.py build_ext -I/path/to/gdal/include -L/path/to/gdal/lib -lgdal --gdalversion 2 develop
-  (fiona_env)$ pytest
-
-.. _GDAL: https://gdal.org
-.. _pyproj: https://pypi.org/project/pyproj/
-.. _Rtree: https://pypi.org/project/Rtree/
-.. _Shapely: https://pypi.org/project/Shapely/
-.. _gdal-config: https://gdal.org/programs/gdal-config.html
+* Fiona `home page <https://github.com/Toblerity/Fiona>`__
+* `Docs and manual <https://fiona.readthedocs.io/>`__
+* `Examples <https://github.com/Toblerity/Fiona/tree/master/examples>`__
+* Main `user discussion group <https://fiona.groups.io/g/main>`__
+* `Developers discussion group <https://fiona.groups.io/g/dev>`__


=====================================
docs/index.rst
=====================================
@@ -5,6 +5,7 @@ Fiona Documentation Contents
    :maxdepth: 2
 
    README
+   Installation <install>
    User Manual <manual>
    API Documentation <modules>
    CLI Documentation <cli>


=====================================
docs/install.rst
=====================================
@@ -0,0 +1,76 @@
+============
+Installation
+============
+
+Installation of the Fiona package is complicated by its dependency on libgdal
+and other C libraries. There are easy installations paths and an advanced
+installation path.
+
+Easy installation
+=================
+
+Fiona has several `extension modules
+<https://docs.python.org/3/extending/extending.html>`__ which link against
+libgdal. This complicates installation. Binary distributions (wheels)
+containing libgdal and its own dependencies are available from the Python
+Package Index and can be installed using pip.
+
+.. code-block:: console
+
+    pip install fiona
+
+These wheels are mainly intended to make installation easy for simple
+applications, not so much for production. They are not tested for compatibility
+with all other binary wheels, conda packages, or QGIS, and omit many of GDAL's
+optional format drivers. If you need, for example, GML support you will need to
+build and install Fiona from a source distribution.
+
+Many users find Anaconda and conda-forge a good way to install Fiona and get
+access to more optional format drivers (like GML).
+
+Fiona 1.9 (coming soon) requires Python 3.7 or higher and GDAL 3.2 or higher.
+
+Advanced installation
+=====================
+
+Once GDAL and its dependencies are installed on your computer (how to do this
+is documented at https://gdal.org) Fiona can be built and installed using
+setuptools or pip. If your GDAL installation provides the ``gdal-config``
+program, the process is simpler.
+
+Without pip:
+
+.. code-block:: console
+
+    GDAL_CONFIG=/path/to/gdal-config python setup.py install
+
+With pip:
+
+.. code-block:: console
+
+    GDAL_CONFIG=/path/to/gdal-config python -m pip install --user .
+
+These are pretty much equivalent. Pip will use setuptools as the build backend.
+If the gdal-config program is on your executable path, then you don't need to
+set the environment variable.
+
+Without gdal-config you will need to configure header and library locations for
+the build in another way. One way to do this is to create a setup.cfg file in
+the source directory with content like this:
+
+.. code-block:: ini
+
+    [build_ext]
+    include_dirs = C:/vcpkg/installed/x64-windows/include
+    libraries = gdal
+    library_dirs = C:/vcpkg/installed/x64-windows/lib
+
+This is the approach taken by Fiona's `wheel-building workflow
+<https://github.com/sgillies/fiona-wheels/blob/master/.github/workflows/win-wheels.yaml#L67-L74>`__.
+With this file in place you can run either ``python setup.py install`` or ``python
+-m pip install --user .``.
+
+You can also pass those three values on the command line following the
+`setuptools documentation
+<https://setuptools.pypa.io/en/latest/userguide/ext_modules.html#compiler-and-linker-options>`__.
+However, the setup.cfg approach is easier.


=====================================
docs/manual.rst
=====================================
@@ -5,7 +5,7 @@ The Fiona User Manual
 :Author: Sean Gillies, <sean.gillies at gmail.com>
 :Version: |release|
 :Date: |today|
-:Copyright: 
+:Copyright:
   This work is licensed under a `Creative Commons Attribution 3.0
   United States License`__.
 
@@ -88,23 +88,24 @@ In what cases would you not benefit from using Fiona?
 Example
 -------
 
-The first example of using Fiona is this: copying records from one file to
-another, adding two attributes and making sure that all polygons are facing
-"up". Orientation of polygons is significant in some applications, extruded
-polygons in Google Earth for one. No other library (like :py:mod:`Shapely`) is
-needed here, which keeps it uncomplicated. There's a :file:`test_uk` file in
-the Fiona repository for use in this and other examples.
+The first example of using Fiona is this: copying features (another word for
+record) from one file to another, adding two attributes and making sure that
+all polygons are facing "up". Orientation of polygons is significant in some
+applications, extruded polygons in Google Earth for one. No other library (like
+:py:mod:`Shapely`) is needed here, which keeps it uncomplicated. There's a
+:file:`test_uk` file in the Fiona repository for use in this and other
+examples.
 
 .. code-block:: python
 
   import datetime
   import logging
   import sys
-  
+
   import fiona
-  
+
   logging.basicConfig(stream=sys.stderr, level=logging.INFO)
-  
+
   def signed_area(coords):
       """Return the signed area enclosed by a ring using the linear time
       algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value >= 0
@@ -112,17 +113,17 @@ the Fiona repository for use in this and other examples.
       """
       xs, ys = map(list, zip(*coords))
       xs.append(xs[1])
-      ys.append(ys[1]) 
+      ys.append(ys[1])
       return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(coords)))/2.0
-  
+
   with fiona.open('docs/data/test_uk.shp', 'r') as source:
-      
+
       # Copy the source schema and add two new properties.
       sink_schema = source.schema
       sink_schema['properties']['s_area'] = 'float'
       sink_schema['properties']['timestamp'] = 'datetime'
-      
-      # Create a sink for processed features with the same format and 
+
+      # Create a sink for processed features with the same format and
       # coordinate reference system as the source.
       with fiona.open(
               'oriented-ccw.shp', 'w',
@@ -130,11 +131,11 @@ the Fiona repository for use in this and other examples.
               driver=source.driver,
               schema=sink_schema,
               ) as sink:
-          
+
           for f in source:
-              
+
               try:
-  
+
                   # If any feature's polygon is facing "down" (has rings
                   # wound clockwise), its rings will be reordered to flip
                   # it "up".
@@ -146,15 +147,15 @@ the Fiona repository for use in this and other examples.
                       rings = [r[::-1] for r in rings]
                       g['coordinates'] = rings
                       f['geometry'] = g
-  
+
                   # Add the signed area of the polygon and a timestamp
                   # to the feature properties map.
                   f['properties'].update(
                       s_area=sa,
                       timestamp=datetime.datetime.now().isoformat() )
-  
+
                   sink.write(f)
-              
+
               except Exception as e:
                   logging.exception("Error processing feature %s:", f['id'])
 
@@ -199,7 +200,7 @@ not treat features as dictionaries? Use of existing Python idioms is one of
 Fiona's major design principles.
 
 .. admonition:: TL;DR
-   
+
    Fiona subscribes to the conventional record model of data, but provides
    GeoJSON-like access to the data via Python file-like and mapping protocols.
 
@@ -221,7 +222,7 @@ Reading a GIS vector file begins by opening it in mode ``'r'`` using Fiona's
 
 .. admonition:: API Change
 
-   :py:func:`fiona.collection` is deprecated, but aliased to 
+   :py:func:`fiona.collection` is deprecated, but aliased to
    :py:func:`fiona.open` in version 0.9.
 
 Mode ``'r'`` is the default and will be omitted in following examples.
@@ -264,7 +265,7 @@ collection to get back to the beginning.
    encoding of the Natural Earth dataset is Windows-1252. In this case, the
    proper encoding can be specified explicitly by using the ``encoding``
    keyword parameter of :py:func:`fiona.open`: ``encoding='Windows-1252'``.
-   
+
    New in version 0.9.1.
 
 Collection indexing
@@ -318,7 +319,7 @@ is a context guard, it is closed no matter what happens within the block.
   ... except:
   ...     print(c.closed)
   ...     raise
-  ... 
+  ...
   48
   True
   Traceback (most recent call last):
@@ -329,7 +330,7 @@ An exception is raised in the :keyword:`with` block above, but as you can see
 from the print statement in the :keyword:`except` clause :py:meth:`c.__exit__`
 (and thereby :py:meth:`c.close`) has been called.
 
-.. important:: Always call :py:meth:`~fiona.collection.Collection.close` or 
+.. important:: Always call :py:meth:`~fiona.collection.Collection.close` or
    use :keyword:`with` and you'll never stumble over tied-up external resources,
    locked files, etc.
 
@@ -422,7 +423,7 @@ dict with items having the same order as the fields in the data file.
                   'CNTRY_NAME': 'str',
                   'AREA': 'float:15.2',
                   'POP_CNTRY': 'float:15.2'}}
-  
+
 Keeping Schemas Simple
 ----------------------
 
@@ -719,7 +720,7 @@ A vector file can be opened for writing in mode ``'a'`` (append) or mode
 ``'w'`` (write).
 
 .. admonition:: Note
-   
+
    The in situ "update" mode of :program:`OGR` is quite format dependent
    and is therefore not supported by Fiona.
 
@@ -752,7 +753,7 @@ of the file grows from 48 to 49.
   ...     print(len(c))
   ...     c.write(rec)
   ...     print(len(c))
-  ... 
+  ...
   48
   49
 
@@ -763,7 +764,7 @@ type of record, remember). You'll get a :py:class:`ValueError` if it doesn't.
 
   >>> with fiona.open('/tmp/test_uk.shp', 'a') as c:
   ...     c.write({'properties': {'foo': 'bar'}})
-  ... 
+  ...
   Traceback (most recent call last):
     ...
   ValueError: Record data not match collection schema
@@ -794,7 +795,7 @@ iterator) of records.
   >>> with fiona.open('/tmp/test_uk.shp', 'a') as c:
   ...     c.writerecords([rec, rec, rec])
   ...     print(len(c))
-  ... 
+  ...
   52
 
 .. admonition:: Duplication
@@ -810,6 +811,11 @@ iterator) of records.
    You may also call :py:meth:`flush` periodically to write the buffer contents
    to disk.
 
+.. admonition:: Format requirements
+
+   Format drivers may have specific requirements about what they store. For
+   example, the Shapefile driver may "fix" topologically invalid features.
+
 Creating files of the same structure
 ------------------------------------
 
@@ -827,7 +833,7 @@ Review the parameters of our demo file.
   ...     source_driver = source.driver
   ...     source_crs = source.crs
   ...     source_schema = source.schema
-  ... 
+  ...
   >>> source_driver
   'ESRI Shapefile'
   >>> source_crs
@@ -853,7 +859,7 @@ We can create a new file using them.
   ...     print(len(c))
   ...     c.write(rec)
   ...     print(len(c))
-  ... 
+  ...
   0
   1
   >>> c.closed
@@ -870,7 +876,7 @@ same order as those of the source file.
   $ ogrinfo /tmp/foo.shp foo -so
   INFO: Open of `/tmp/foo.shp'
         using driver `ESRI Shapefile' successful.
-  
+
   Layer name: foo
   Geometry: 3D Polygon
   Feature Count: 1
@@ -987,7 +993,7 @@ dict is given, the ordering is determined by the output of that dict's
 For example, since
 
 .. code-block:: pycon
-  
+
   >>> {'bar': 'int', 'foo': 'str'}.keys()
   ['foo', 'bar']
 
@@ -1066,13 +1072,13 @@ If you call ``fiona.open()`` with no surrounding ``Env`` environment, one will
 be created for you.
 
 When your program exits the environment's with block the configuration reverts
-to its previous state. 
+to its previous state.
 
 Driver configuration options
 ----------------------------
 
 Drivers can have dataset open, dataset creation, respectively layer creation options. These options can be found
-on the drivers page on `GDAL's homepage. <https://gdal.org/drivers/vector/index.html>`_ or using the 
+on the drivers page on `GDAL's homepage. <https://gdal.org/drivers/vector/index.html>`_ or using the
 ``fiona.meta`` module:
 
 .. code-block:: pycon
@@ -1148,7 +1154,7 @@ builtin :py:func:`list` as shown below.
   7
 
 The iterator method takes the same ``stop`` or ``start, stop[, step]``
-slicing arguments as :py:func:`itertools.islice`. 
+slicing arguments as :py:func:`itertools.islice`.
 To get just the first two items from that iterator, pass a stop index.
 
 .. code-block:: pycon
@@ -1328,7 +1334,7 @@ The single shapefile may also be accessed like so:
 .. code-block:: pycon
 
   >>> with fiona.open(
-  ...         '/docs/data/test_uk.shp', 
+  ...         '/docs/data/test_uk.shp',
   ...         vfs='zip:///tmp/zed.zip') as c:
   ...     print(len(c))
   ...
@@ -1340,10 +1346,10 @@ Unsupported 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` 
+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.  
+However, there are no guarantees that Fiona will be able to access or write
+data correctly using an unsupported driver.
 
 .. code-block:: python
 
@@ -1352,7 +1358,7 @@ data correctly using an unsupported driver.
   with fiona.open("file.kmz", allow_unsupported_drivers=True) as collection:
     ...
 
-Not all OGR drivers are necessarily enabled in every GDAL distribution. The 
+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:
 
@@ -1399,7 +1405,7 @@ in an instance of ZipMemoryFile.
 Fiona command line interface
 ============================
 
-Fiona comes with a command line interface called "fio". See the 
+Fiona comes with a command line interface called "fio". See the
 `CLI Documentation <cli.html>`__ for detailed usage instructions.
 
 Final Notes


=====================================
fiona/__init__.py
=====================================
@@ -85,29 +85,30 @@ if platform.system() == "Windows":
                     os.add_dll_directory(p)
 
 
-from fiona.collection import BytesCollection, Collection
-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_num,
     get_gdal_version_tuple,
 )
+from fiona._env import driver_count
+from fiona._show_versions import show_versions
+from fiona.collection import BytesCollection, Collection
+from fiona.drvsupport import supported_drivers
+from fiona.env import ensure_env_with_credentials, Env
+from fiona.errors import FionaDeprecationWarning
 from fiona.io import MemoryFile
+from fiona.model import Feature, Geometry, Properties
 from fiona.ogrext import (
+    FIELD_TYPES_MAP,
     _bounds,
-    _listlayers,
     _listdir,
-    FIELD_TYPES_MAP,
+    _listlayers,
     _remove,
     _remove_layer,
 )
 from fiona.path import ParsedPath, parse_path, vsi_path
 from fiona.vfs import parse_paths as vfs_parse_paths
-from fiona._show_versions import show_versions
 
 # These modules are imported by fiona.ogrext, but are also import here to
 # help tools like cx_Freeze find them automatically
@@ -115,8 +116,18 @@ from fiona import _geometry, _err, rfc3339
 import uuid
 
 
-__all__ = ['bounds', 'listlayers', 'listdir', 'open', 'prop_type', 'prop_width']
-__version__ = "1.9b1"
+__all__ = [
+    "Feature",
+    "Geometry",
+    "Properties",
+    "bounds",
+    "listlayers",
+    "listdir",
+    "open",
+    "prop_type",
+    "prop_width",
+]
+__version__ = "1.9b2dev"
 __gdal_version__ = get_gdal_release_name()
 
 gdal_version = get_gdal_version_tuple()


=====================================
fiona/_geometry.pyx
=====================================
@@ -7,7 +7,7 @@ include "gdal.pxi"
 import logging
 
 from fiona.errors import UnsupportedGeometryTypeError
-from fiona.model import _guard_model_object, GEOMETRY_TYPES, Geometry, OGRGeometryType
+from fiona.model import decode_object, GEOMETRY_TYPES, Geometry, OGRGeometryType
 from fiona._err cimport exc_wrap_int
 
 
@@ -363,7 +363,7 @@ cdef class OGRGeomBuilder:
 
 def geometryRT(geom):
     # For testing purposes only, leaks the JSON data
-    geometry = _guard_model_object(geom)
+    geometry = decode_object(geom)
     cdef void *cogr_geometry = OGRGeomBuilder().build(geometry)
     result = GeomBuilder().build(cogr_geometry)
     _deleteOgrGeom(cogr_geometry)


=====================================
fiona/fio/bounds.py
=====================================
@@ -1,8 +1,6 @@
 """$ fio bounds"""
 
-
 import json
-import logging
 
 import click
 from cligj import precision_opt, use_rs_opt
@@ -38,56 +36,30 @@ def bounds(ctx, precision, explode, with_id, with_obj, use_rs):
     To print the input objects themselves along with their bounds
     as GeoJSON object, use --with-obj. This has the effect of updating
     input objects with {id: identifier, bbox: bounds}.
+
     """
-    logger = logging.getLogger(__name__)
     stdin = click.get_text_stream('stdin')
+    source = obj_gen(stdin)
 
-    try:
-        source = obj_gen(stdin)
-
-        for i, obj in enumerate(source):
-            obj_id = obj.get('id', 'collection:' + str(i))
-            xs = []
-            ys = []
-            features = obj.get('features') or [obj]
-
-            for j, feat in enumerate(features):
-                feat_id = feat.get('id', 'feature:' + str(i))
-                w, s, e, n = fiona.bounds(feat)
+    for i, obj in enumerate(source):
+        obj_id = obj.get("id", "collection:" + str(i))
+        xs = []
+        ys = []
+        features = obj.get("features") or [obj]
 
-                if precision > 0:
-                    w, s, e, n = (round(v, precision)
-                                  for v in (w, s, e, n))
-                if explode:
+        for j, feat in enumerate(features):
+            feat_id = feat.get("id", "feature:" + str(i))
+            w, s, e, n = fiona.bounds(feat)
 
-                    if with_id:
-                        rec = {
-                            'parent': obj_id,
-                            'id': feat_id,
-                            'bbox': (w, s, e, n)}
-                    elif with_obj:
-                        feat.update(parent=obj_id, bbox=(w, s, e, n))
-                        rec = feat
-                    else:
-                        rec = (w, s, e, n)
-
-                    if use_rs:
-                        click.echo('\x1e', nl=False)
-
-                    click.echo(json.dumps(rec, cls=ObjectEncoder))
-
-                else:
-                    xs.extend([w, e])
-                    ys.extend([s, n])
-
-            if not explode:
-                w, s, e, n = (min(xs), min(ys), max(xs), max(ys))
+            if precision > 0:
+                w, s, e, n = (round(v, precision) for v in (w, s, e, n))
+            if explode:
 
                 if with_id:
-                    rec = {'id': obj_id, 'bbox': (w, s, e, n)}
+                    rec = {"parent": obj_id, "id": feat_id, "bbox": (w, s, e, n)}
                 elif with_obj:
-                    obj.update(id=obj_id, bbox=(w, s, e, n))
-                    rec = obj
+                    feat.update(parent=obj_id, bbox=(w, s, e, n))
+                    rec = feat
                 else:
                     rec = (w, s, e, n)
 
@@ -96,6 +68,22 @@ def bounds(ctx, precision, explode, with_id, with_obj, use_rs):
 
                 click.echo(json.dumps(rec, cls=ObjectEncoder))
 
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+            else:
+                xs.extend([w, e])
+                ys.extend([s, n])
+
+        if not explode:
+            w, s, e, n = (min(xs), min(ys), max(xs), max(ys))
+
+            if with_id:
+                rec = {"id": obj_id, "bbox": (w, s, e, n)}
+            elif with_obj:
+                obj.update(id=obj_id, bbox=(w, s, e, n))
+                rec = obj
+            else:
+                rec = (w, s, e, n)
+
+            if use_rs:
+                click.echo("\x1e", nl=False)
+
+            click.echo(json.dumps(rec, cls=ObjectEncoder))


=====================================
fiona/fio/calc.py
=====================================
@@ -1,6 +1,5 @@
 from __future__ import division
 import json
-import logging
 
 import click
 from cligj import use_rs_opt
@@ -39,27 +38,27 @@ def calc(ctx, property_name, expression, overwrite, use_rs):
 
     \b
     $ fio cat data.shp | fio calc sumAB  "f.properties.A + f.properties.B"
+
     """
-    logger = logging.getLogger(__name__)
     stdin = click.get_text_stream('stdin')
-    try:
-        source = obj_gen(stdin)
-        for i, obj in enumerate(source):
-            features = obj.get('features') or [obj]
-            for j, feat in enumerate(features):
+    source = obj_gen(stdin)
+
+    for i, obj in enumerate(source):
+        features = obj.get("features") or [obj]
+
+        for j, feat in enumerate(features):
 
-                if not overwrite and property_name in feat['properties']:
-                    raise click.UsageError(
-                        '{0} already exists in properties; '
-                        'rename or use --overwrite'.format(property_name))
+            if not overwrite and property_name in feat["properties"]:
+                raise click.UsageError(
+                    "{0} already exists in properties; "
+                    "rename or use --overwrite".format(property_name)
+                )
 
-                feat['properties'][property_name] = eval_feature_expression(
-                    feat, expression)
+            feat["properties"][property_name] = eval_feature_expression(
+                feat, expression
+            )
 
-                if use_rs:
-                    click.echo('\x1e', nl=False)
-                click.echo(json.dumps(feat, cls=ObjectEncoder))
+            if use_rs:
+                click.echo("\x1e", nl=False)
 
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+            click.echo(json.dumps(feat, cls=ObjectEncoder))


=====================================
fiona/fio/cat.py
=====================================
@@ -1,7 +1,6 @@
 """fio-cat"""
 
 import json
-import logging
 import warnings
 
 import click
@@ -80,8 +79,6 @@ def cat(
     Use the '--layer' option to select a different layer.
 
     """
-    log = logging.getLogger(__name__)
-
     dump_kwds = {"sort_keys": True}
     if indent:
         dump_kwds["indent"] = indent
@@ -104,11 +101,13 @@ def cat(
                 bbox = tuple(map(float, bbox.split(",")))
             except ValueError:
                 bbox = json.loads(bbox)
+
         for i, path in enumerate(files, 1):
             for lyr in layer[str(i)]:
                 with fiona.open(path, layer=lyr) as src:
                     for i, feat in src.items(bbox=bbox, where=where):
                         geom = feat.geometry
+
                         if dst_crs:
                             geom = transform_geom(
                                 src.crs,
@@ -126,12 +125,11 @@ def cat(
                             geometry=geom,
                             bbox=fiona.bounds(geom),
                         )
+
                         if use_rs:
                             click.echo("\x1e", nl=False)
+
                         click.echo(json.dumps(feat, cls=ObjectEncoder, **dump_kwds))
 
     except AttributeFilterError as e:
         raise click.BadParameter("'where' clause is invalid: " + str(e))
-    except Exception:
-        log.exception("Exception caught during processing")
-        raise click.Abort()


=====================================
fiona/fio/collect.py
=====================================
@@ -150,47 +150,74 @@ def collect(
                 for line in stdin:
                     yield line
 
-    try:
-        source = feature_text_gen()
+    source = feature_text_gen()
 
-        if record_buffered:
-            # Buffer GeoJSON data at the feature level for smaller
-            # memory footprint.
-            indented = bool(indent)
-            rec_indent = "\n" + " " * (2 * (indent or 0))
+    if record_buffered:
+        # Buffer GeoJSON data at the feature level for smaller
+        # memory footprint.
+        indented = bool(indent)
+        rec_indent = "\n" + " " * (2 * (indent or 0))
 
-            collection = {"type": "FeatureCollection", "features": []}
-            if with_ld_context:
-                collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+        collection = {"type": "FeatureCollection", "features": []}
+        if with_ld_context:
+            collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+
+        head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
 
-            head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split(
-                "[]"
-            )
+        sink.write(head)
+        sink.write("[")
 
-            sink.write(head)
-            sink.write("[")
+        # Try the first record.
+        try:
+            i, first = 0, next(source)
+            if with_ld_context:
+                first = helpers.id_record(first)
+            if indented:
+                sink.write(rec_indent)
+            sink.write(first.replace("\n", rec_indent))
+        except StopIteration:
+            pass
+        except Exception as exc:
+            # Ignoring errors is *not* the default.
+            if ignore_errors:
+                logger.error(
+                    "failed to serialize file record %d (%s), " "continuing", i, exc
+                )
+            else:
+                # Log error and close up the GeoJSON, leaving it
+                # more or less valid no matter what happens above.
+                logger.critical(
+                    "failed to serialize file record %d (%s), " "quiting", i, exc
+                )
+                sink.write("]")
+                sink.write(tail)
+                if indented:
+                    sink.write("\n")
+                raise
 
-            # Try the first record.
+        # Because trailing commas aren't valid in JSON arrays
+        # we'll write the item separator before each of the
+        # remaining features.
+        for i, rec in enumerate(source, 1):
             try:
-                i, first = 0, next(source)
                 if with_ld_context:
-                    first = helpers.id_record(first)
+                    rec = helpers.id_record(rec)
                 if indented:
                     sink.write(rec_indent)
-                sink.write(first.replace("\n", rec_indent))
-            except StopIteration:
-                pass
+                sink.write(item_sep)
+                sink.write(rec.replace("\n", rec_indent))
             except Exception as exc:
-                # Ignoring errors is *not* the default.
                 if ignore_errors:
                     logger.error(
-                        "failed to serialize file record %d (%s), " "continuing", i, exc
+                        "failed to serialize file record %d (%s), " "continuing",
+                        i,
+                        exc,
                     )
                 else:
-                    # Log error and close up the GeoJSON, leaving it
-                    # more or less valid no matter what happens above.
                     logger.critical(
-                        "failed to serialize file record %d (%s), " "quiting", i, exc
+                        "failed to serialize file record %d (%s), " "quiting",
+                        i,
+                        exc,
                     )
                     sink.write("]")
                     sink.write(tail)
@@ -198,58 +225,22 @@ def collect(
                         sink.write("\n")
                     raise
 
-            # Because trailing commas aren't valid in JSON arrays
-            # we'll write the item separator before each of the
-            # remaining features.
-            for i, rec in enumerate(source, 1):
-                try:
-                    if with_ld_context:
-                        rec = helpers.id_record(rec)
-                    if indented:
-                        sink.write(rec_indent)
-                    sink.write(item_sep)
-                    sink.write(rec.replace("\n", rec_indent))
-                except Exception as exc:
-                    if ignore_errors:
-                        logger.error(
-                            "failed to serialize file record %d (%s), " "continuing",
-                            i,
-                            exc,
-                        )
-                    else:
-                        logger.critical(
-                            "failed to serialize file record %d (%s), " "quiting",
-                            i,
-                            exc,
-                        )
-                        sink.write("]")
-                        sink.write(tail)
-                        if indented:
-                            sink.write("\n")
-                        raise
-
-            # Close up the GeoJSON after writing all features.
-            sink.write("]")
-            sink.write(tail)
-            if indented:
-                sink.write("\n")
-
-        else:
-            # Buffer GeoJSON data at the collection level. The default.
-            collection = {"type": "FeatureCollection", "features": []}
-            if with_ld_context:
-                collection["@context"] = helpers.make_ld_context(add_ld_context_item)
-
-            head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split(
-                "[]"
-            )
-            sink.write(head)
-            sink.write("[")
-            sink.write(",".join(source))
-            sink.write("]")
-            sink.write(tail)
+        # Close up the GeoJSON after writing all features.
+        sink.write("]")
+        sink.write(tail)
+        if indented:
             sink.write("\n")
 
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+    else:
+        # Buffer GeoJSON data at the collection level. The default.
+        collection = {"type": "FeatureCollection", "features": []}
+        if with_ld_context:
+            collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+
+        head, tail = json.dumps(collection, cls=ObjectEncoder, **dump_kwds).split("[]")
+        sink.write(head)
+        sink.write("[")
+        sink.write(",".join(source))
+        sink.write("]")
+        sink.write(tail)
+        sink.write("\n")


=====================================
fiona/fio/distrib.py
=====================================
@@ -1,8 +1,6 @@
 """$ fio distrib"""
 
-
 import json
-import logging
 
 import click
 import cligj
@@ -21,23 +19,17 @@ def distrib(ctx, use_rs):
     Print the features of GeoJSON objects read from stdin.
 
     """
-    logger = logging.getLogger(__name__)
     stdin = click.get_text_stream('stdin')
-
-    try:
-        source = helpers.obj_gen(stdin)
-        for i, obj in enumerate(source):
-            obj_id = obj.get('id', 'collection:' + str(i))
-            features = obj.get('features') or [obj]
-            for j, feat in enumerate(features):
-                if obj.get('type') == 'FeatureCollection':
-                    feat['parent'] = obj_id
-                feat_id = feat.get('id', 'feature:' + str(i))
-                feat['id'] = feat_id
-                if use_rs:
-                    click.echo('\x1e', nl=False)
-                click.echo(json.dumps(feat, cls=ObjectEncoder))
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+    source = helpers.obj_gen(stdin)
+
+    for i, obj in enumerate(source):
+        obj_id = obj.get("id", "collection:" + str(i))
+        features = obj.get("features") or [obj]
+        for j, feat in enumerate(features):
+            if obj.get("type") == "FeatureCollection":
+                feat["parent"] = obj_id
+            feat_id = feat.get("id", "feature:" + str(i))
+            feat["id"] = feat_id
+            if use_rs:
+                click.echo("\x1e", nl=False)
+            click.echo(json.dumps(feat, cls=ObjectEncoder))


=====================================
fiona/fio/dump.py
=====================================
@@ -1,6 +1,5 @@
 """fio-dump"""
 
-
 from functools import partial
 import json
 import logging
@@ -63,56 +62,88 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
                      antimeridian_cutting=True, precision=precision)
         return Feature(id=feat.id, properties=feat.properties, geometry=tg(feat.geometry))
 
-    try:
-        with fiona.open(input, **open_kwds) as source:
-            meta = source.meta
-            meta['fields'] = dict(source.schema['properties'].items())
-
-            if record_buffered:
-                # Buffer GeoJSON data at the feature level for smaller
-                # memory footprint.
-                indented = bool(indent)
-                rec_indent = "\n" + " " * (2 * (indent or 0))
-
-                collection = {
-                    'type': 'FeatureCollection',
-                    'fiona:schema': meta['schema'],
-                    'fiona:crs': meta['crs'],
-                    'features': []}
-                if with_ld_context:
-                    collection['@context'] = helpers.make_ld_context(
-                        add_ld_context_item)
+    with fiona.open(input, **open_kwds) as source:
+        meta = source.meta
+        meta["fields"] = dict(source.schema["properties"].items())
+
+        if record_buffered:
+            # Buffer GeoJSON data at the feature level for smaller
+            # memory footprint.
+            indented = bool(indent)
+            rec_indent = "\n" + " " * (2 * (indent or 0))
+
+            collection = {
+                "type": "FeatureCollection",
+                "fiona:schema": meta["schema"],
+                "fiona:crs": meta["crs"],
+                "features": [],
+            }
+            if with_ld_context:
+                collection["@context"] = helpers.make_ld_context(add_ld_context_item)
 
-                head, tail = json.dumps(
-                    collection, **dump_kwds).split('[]')
+            head, tail = json.dumps(collection, **dump_kwds).split("[]")
 
-                sink.write(head)
-                sink.write("[")
+            sink.write(head)
+            sink.write("[")
 
-                itr = iter(source)
+            itr = iter(source)
 
-                # Try the first record.
+            # Try the first record.
+            try:
+                i, first = 0, next(itr)
+                first = transformer(first)
+                if with_ld_context:
+                    first = helpers.id_record(first)
+                if indented:
+                    sink.write(rec_indent)
+                sink.write(
+                    json.dumps(first, cls=ObjectEncoder, **dump_kwds).replace(
+                        "\n", rec_indent
+                    )
+                )
+            except StopIteration:
+                pass
+            except Exception as exc:
+                # Ignoring errors is *not* the default.
+                if ignore_errors:
+                    logger.error(
+                        "failed to serialize file record %d (%s), " "continuing", i, exc
+                    )
+                else:
+                    # Log error and close up the GeoJSON, leaving it
+                    # more or less valid no matter what happens above.
+                    logger.critical(
+                        "failed to serialize file record %d (%s), " "quiting", i, exc
+                    )
+                    sink.write("]")
+                    sink.write(tail)
+                    if indented:
+                        sink.write("\n")
+                    raise
+
+            # Because trailing commas aren't valid in JSON arrays
+            # we'll write the item separator before each of the
+            # remaining features.
+            for i, rec in enumerate(itr, 1):
+                rec = transformer(rec)
                 try:
-                    i, first = 0, next(itr)
-                    first = transformer(first)
                     if with_ld_context:
-                        first = helpers.id_record(first)
+                        rec = helpers.id_record(rec)
                     if indented:
                         sink.write(rec_indent)
-                    sink.write(json.dumps(
-                        first, cls=ObjectEncoder, **dump_kwds).replace("\n", rec_indent))
-                except StopIteration:
-                    pass
+                    sink.write(item_sep)
+                    sink.write(
+                        json.dumps(rec, cls=ObjectEncoder, **dump_kwds).replace(
+                            "\n", rec_indent
+                        )
+                    )
                 except Exception as exc:
-                    # Ignoring errors is *not* the default.
                     if ignore_errors:
                         logger.error(
                             "failed to serialize file record %d (%s), "
                             "continuing",
                             i, exc)
                     else:
-                        # Log error and close up the GeoJSON, leaving it
-                        # more or less valid no matter what happens above.
                         logger.critical(
                             "failed to serialize file record %d (%s), "
                             "quiting",
@@ -123,60 +154,26 @@ def dump(ctx, input, encoding, precision, indent, compact, record_buffered,
                             sink.write("\n")
                         raise
 
-                # Because trailing commas aren't valid in JSON arrays
-                # we'll write the item separator before each of the
-                # remaining features.
-                for i, rec in enumerate(itr, 1):
-                    rec = transformer(rec)
-                    try:
-                        if with_ld_context:
-                            rec = helpers.id_record(rec)
-                        if indented:
-                            sink.write(rec_indent)
-                        sink.write(item_sep)
-                        sink.write(json.dumps(
-                            rec, cls=ObjectEncoder, **dump_kwds).replace("\n", rec_indent))
-                    except Exception as exc:
-                        if ignore_errors:
-                            logger.error(
-                                "failed to serialize file record %d (%s), "
-                                "continuing",
-                                i, exc)
-                        else:
-                            logger.critical(
-                                "failed to serialize file record %d (%s), "
-                                "quiting",
-                                i, exc)
-                            sink.write("]")
-                            sink.write(tail)
-                            if indented:
-                                sink.write("\n")
-                            raise
-
-                # Close up the GeoJSON after writing all features.
-                sink.write("]")
-                sink.write(tail)
-                if indented:
-                    sink.write("\n")
-
+            # Close up the GeoJSON after writing all features.
+            sink.write("]")
+            sink.write(tail)
+            if indented:
+                sink.write("\n")
+
+        else:
+            # Buffer GeoJSON data at the collection level. The default.
+            collection = {
+                "type": "FeatureCollection",
+                "fiona:schema": meta["schema"],
+                "fiona:crs": meta["crs"].to_string(),
+            }
+            if with_ld_context:
+                collection["@context"] = helpers.make_ld_context(add_ld_context_item)
+                collection["features"] = [
+                    helpers.id_record(transformer(rec)) for rec in source
+                ]
             else:
-                # Buffer GeoJSON data at the collection level. The default.
-                collection = {
-                    "type": "FeatureCollection",
-                    "fiona:schema": meta["schema"],
-                    "fiona:crs": meta["crs"].to_string(),
-                }
-                if with_ld_context:
-                    collection['@context'] = helpers.make_ld_context(
-                        add_ld_context_item)
-                    collection['features'] = [
-                        helpers.id_record(transformer(rec))
-                        for rec in source]
-                else:
-                    collection['features'] = [
-                        transformer(source.crs, rec) for rec in source]
-                json.dump(collection, sink, cls=ObjectEncoder, **dump_kwds)
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+                collection["features"] = [
+                    transformer(source.crs, rec) for rec in source
+                ]
+            json.dump(collection, sink, cls=ObjectEncoder, **dump_kwds)


=====================================
fiona/fio/filter.py
=====================================
@@ -1,7 +1,6 @@
 """$ fio filter"""
 
 import json
-import logging
 
 import click
 from cligj import use_rs_opt
@@ -10,9 +9,6 @@ from fiona.fio.helpers import obj_gen, eval_feature_expression
 from fiona.fio import with_context_env
 
 
-logger = logging.getLogger(__name__)
-
-
 @click.command()
 @click.argument('filter_expression')
 @use_rs_opt
@@ -41,19 +37,14 @@ def filter(ctx, filter_expression, use_rs):
 
     """
     stdin = click.get_text_stream('stdin')
+    source = obj_gen(stdin)
+
+    for i, obj in enumerate(source):
+        features = obj.get("features") or [obj]
+        for j, feat in enumerate(features):
+            if not eval_feature_expression(feat, filter_expression):
+                continue
 
-    try:
-        source = obj_gen(stdin)
-        for i, obj in enumerate(source):
-            features = obj.get('features') or [obj]
-            for j, feat in enumerate(features):
-                if not eval_feature_expression(feat, filter_expression):
-                    continue
-
-                if use_rs:
-                    click.echo('\x1e', nl=False)
-                click.echo(json.dumps(feat))
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+            if use_rs:
+                click.echo("\x1e", nl=False)
+            click.echo(json.dumps(feat))


=====================================
fiona/fio/info.py
=====================================
@@ -12,6 +12,8 @@ import fiona.crs
 from fiona.errors import DriverError
 from fiona.fio import options, with_context_env
 
+logger = logging.getLogger(__name__)
+
 
 @click.command()
 # One or more files.
@@ -42,36 +44,34 @@ def info(ctx, input, indent, meta_member, layer):
 
     When working with a multi-layer dataset the first layer is used by default.
     Use the '--layer' option to select a different layer.
-    """
 
-    logger = logging.getLogger(__name__)
-    try:
-        with fiona.open(input, layer=layer) as src:
-            info = src.meta
-            info.update(name=src.name)
+    """
+    with fiona.open(input, layer=layer) as src:
+        info = src.meta
+        info.update(name=src.name)
 
-            try:
-                info.update(bounds=src.bounds)
-            except DriverError:
-                info.update(bounds=None)
-                logger.debug("Setting 'bounds' to None - driver was not able to calculate bounds")
+        try:
+            info.update(bounds=src.bounds)
+        except DriverError:
+            info.update(bounds=None)
+            logger.debug(
+                "Setting 'bounds' to None - driver was not able to calculate bounds"
+            )
 
-            try:
-                info.update(count=len(src))
-            except TypeError:
-                info.update(count=None)
-                logger.debug("Setting 'count' to None/null - layer does not support counting")
+        try:
+            info.update(count=len(src))
+        except TypeError:
+            info.update(count=None)
+            logger.debug(
+                "Setting 'count' to None/null - layer does not support counting"
+            )
 
-            info["crs"] = src.crs.to_string()
+        info["crs"] = src.crs.to_string()
 
-            if meta_member:
-                if isinstance(info[meta_member], (list, tuple)):
-                    click.echo(" ".join(map(str, info[meta_member])))
-                else:
-                    click.echo(info[meta_member])
+        if meta_member:
+            if isinstance(info[meta_member], (list, tuple)):
+                click.echo(" ".join(map(str, info[meta_member])))
             else:
-                click.echo(json.dumps(info, indent=indent))
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+                click.echo(info[meta_member])
+        else:
+            click.echo(json.dumps(info, indent=indent))


=====================================
fiona/fio/insp.py
=====================================
@@ -2,7 +2,6 @@
 
 
 import code
-import logging
 import sys
 
 import click
@@ -18,28 +17,25 @@ from fiona.fio import with_context_env
 @click.pass_context
 @with_context_env
 def insp(ctx, src_path, interpreter):
-    """Open a collection within an interactive interpreter.
-    """
-    logger = logging.getLogger(__name__)
-    banner = 'Fiona %s Interactive Inspector (Python %s)\n' \
-             'Type "src.schema", "next(src)", or "help(src)" ' \
-             'for more information.' \
-             % (fiona.__version__, '.'.join(map(str, sys.version_info[:3])))
-
-    try:
-        with fiona.open(src_path) as src:
-            scope = locals()
-            if not interpreter:
-                code.interact(banner, local=scope)
-            elif interpreter == 'ipython':
-                import IPython
-                IPython.InteractiveShell.banner1 = banner
-                IPython.start_ipython(argv=[], user_ns=scope)
-            else:
-                raise click.ClickException(
-                    'Interpreter {} is unsupported or missing '
-                    'dependencies'.format(interpreter))
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+    """Open a collection within an interactive interpreter."""
+    banner = (
+        "Fiona %s Interactive Inspector (Python %s)\n"
+        'Type "src.schema", "next(src)", or "help(src)" '
+        "for more information."
+        % (fiona.__version__, ".".join(map(str, sys.version_info[:3])))
+    )
+
+    with fiona.open(src_path) as src:
+        scope = locals()
+        if not interpreter:
+            code.interact(banner, local=scope)
+        elif interpreter == "ipython":
+            import IPython
+
+            IPython.InteractiveShell.banner1 = banner
+            IPython.start_ipython(argv=[], user_ns=scope)
+        else:
+            raise click.ClickException(
+                "Interpreter {} is unsupported or missing "
+                "dependencies".format(interpreter)
+            )


=====================================
fiona/fio/load.py
=====================================
@@ -1,8 +1,6 @@
 """$ fio load"""
 
-
 from functools import partial
-import logging
 
 import click
 import cligj
@@ -80,8 +78,6 @@ def load(ctx, output, driver, src_crs, dst_crs, features, layer, creation_option
     GeoJSON feature objects.
 
     """
-    logger = logging.getLogger(__name__)
-
     dst_crs = dst_crs or src_crs
 
     if src_crs and dst_crs and src_crs != dst_crs:
@@ -101,37 +97,39 @@ def load(ctx, output, driver, src_crs, dst_crs, features, layer, creation_option
         Feature
 
         """
-        for feat in features:
-            feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"]))
-            yield Feature.from_dict(**feat)
+        try:
+            for feat in features:
+                feat["geometry"] = transformer(Geometry.from_dict(**feat["geometry"]))
+                yield Feature.from_dict(**feat)
+        except TypeError:
+            raise click.ClickException("Invalid input.")
 
-    try:
-        source = feature_gen()
+    source = feature_gen()
 
-        # Use schema of first feature as a template.
-        # TODO: schema specified on command line?
+    # Use schema of first feature as a template.
+    # TODO: schema specified on command line?
+    try:
         first = next(source)
-        # print(first, first.geometry)
-        schema = {"geometry": first.geometry.type}
-        schema["properties"] = dict(
-            [
-                (k, FIELD_TYPES_MAP_REV.get(type(v)) or "str")
-                for k, v in first.properties.items()
-            ]
-        )
-
-        with fiona.open(
-            output,
-            "w",
-            driver=driver,
-            crs=dst_crs,
-            schema=schema,
-            layer=layer,
-            **creation_options
-        ) as dst:
-            dst.write(first)
-            dst.writerecords(source)
-
-    except Exception:
-        logger.exception("Exception caught during processing")
-        raise click.Abort()
+    except TypeError:
+        raise click.ClickException("Invalid input.")
+
+    # print(first, first.geometry)
+    schema = {"geometry": first.geometry.type}
+    schema["properties"] = dict(
+        [
+            (k, FIELD_TYPES_MAP_REV.get(type(v)) or "str")
+            for k, v in first.properties.items()
+        ]
+    )
+
+    with fiona.open(
+        output,
+        "w",
+        driver=driver,
+        crs=dst_crs,
+        schema=schema,
+        layer=layer,
+        **creation_options
+    ) as dst:
+        dst.write(first)
+        dst.writerecords(source)


=====================================
fiona/model.py
=====================================
@@ -194,8 +194,8 @@ class Geometry(Object):
         super(Geometry, self).__init__(**data)
 
     @classmethod
-    def from_dict(cls, mapping=None, **kwargs):
-        data = dict(mapping or {}, **kwargs)
+    def from_dict(cls, ob=None, **kwargs):
+        data = dict(getattr(ob, "__geo_interface__", ob) or {}, **kwargs)
         return Geometry(
             coordinates=data.pop("coordinates", None),
             type=data.pop("type", None),
@@ -239,6 +239,10 @@ class Geometry(Object):
         """
         return self._delegate.geometries
 
+    @property
+    def __geo_interface__(self):
+        return ObjectEncoder().default(self)
+
 
 class _Feature(object):
     def __init__(self, geometry=None, id=None, properties=None):
@@ -266,8 +270,8 @@ class Feature(Object):
         super(Feature, self).__init__(**data)
 
     @classmethod
-    def from_dict(cls, mapping=None, **kwargs):
-        data = dict(mapping or {}, **kwargs)
+    def from_dict(cls, ob=None, **kwargs):
+        data = dict(getattr(ob, "__geo_interface__", ob) or {}, **kwargs)
         geom_data = data.pop("geometry", None)
 
         if isinstance(geom_data, Geometry):
@@ -309,7 +313,7 @@ class Feature(Object):
 
         Returns
         ------
-        obejct
+        object
 
         """
         return self._delegate.id
@@ -320,7 +324,7 @@ class Feature(Object):
 
         Returns
         -------
-        Object
+        object
 
         """
         return self._delegate.properties
@@ -336,6 +340,10 @@ class Feature(Object):
         """
         return "Feature"
 
+    @property
+    def __geo_interface__(self):
+        return ObjectEncoder().default(self)
+
 
 class Properties(Object):
     """A GeoJSON-like feature's properties"""
@@ -380,12 +388,17 @@ def decode_object(obj):
     Feature, Geometry, or dict
 
     """
-    if (obj.get("type", None) == "Feature") or "geometry" in obj:
-        return Feature.from_dict(**obj)
-    elif obj.get("type", None) in list(GEOMETRY_TYPES.values())[:8]:
-        return Geometry.from_dict(**obj)
-    else:
+    if isinstance(obj, Object):
         return obj
+    else:
+        obj = obj.get("__geo_interface__", obj)
+
+        if (obj.get("type", None) == "Feature") or "geometry" in obj:
+            return Feature.from_dict(**obj)
+        elif obj.get("type", None) in list(GEOMETRY_TYPES.values())[:8]:
+            return Geometry.from_dict(**obj)
+        else:
+            return obj
 
 
 def to_dict(val):
@@ -396,18 +409,3 @@ def to_dict(val):
         return val
     else:
         return obj
-
-
-def _guard_model_object(obj):
-    """Convert dict to Geometry or Feature.
-
-    For use during the 1.9-2.0 transition. Will be removed in 2.0.
-
-    """
-    if not isinstance(obj, Object):
-        warn(
-            "Support for feature and geometry dicts is deprecated. Instances of Feature and Geometry will be required in 2.0.",
-            FionaDeprecationWarning,
-            stacklevel=2,
-        )
-    return decode_object(obj)


=====================================
fiona/ogrext.pyx
=====================================
@@ -32,8 +32,8 @@ from fiona.env import Env
 from fiona.errors import (
     DriverError, DriverIOError, SchemaError, CRSError, FionaValueError,
     TransactionError, GeometryTypeValidationError, DatasetDeleteError,
-    AttributeFilterError, FeatureWarning, FionaDeprecationWarning)
-from fiona.model import _guard_model_object, Feature, Geometry, Properties
+    AttributeFilterError, FeatureWarning, FionaDeprecationWarning, UnsupportedGeometryTypeError)
+from fiona.model import decode_object, Feature, Geometry, Properties
 from fiona.path import vsi_path
 from fiona.rfc3339 import parse_date, parse_datetime, parse_time
 from fiona.rfc3339 import FionaDateType, FionaDateTimeType, FionaTimeType
@@ -119,14 +119,16 @@ cdef void* gdal_open_vector(char* path_c, int mode, drivers, options) except NUL
                 drvs = CSLAddString(drvs, name_c)
 
     for k, v in options.items():
-        if v is None:
-            continue
-        k = k.upper().encode('utf-8')
-        if isinstance(v, bool):
-            v = ('ON' if v else 'OFF').encode('utf-8')
-        else:
-            v = str(v).encode('utf-8')
-        open_opts = CSLAddNameValue(open_opts, <const char *>k, <const char *>v)
+
+        if v is not None:
+            kb = k.upper().encode('utf-8')
+
+            if isinstance(v, bool):
+                vb = ('ON' if v else 'OFF').encode('utf-8')
+            else:
+                vb = str(v).encode('utf-8')
+
+            open_opts = CSLAddNameValue(open_opts, <const char *>kb, <const char *>vb)
 
     open_opts = CSLAddNameValue(open_opts, "VALIDATE_OPEN_OPTIONS", "NO")
 
@@ -148,13 +150,26 @@ cdef void* gdal_create(void* cogr_driver, const char *path_c, options) except NU
     cdef char **creation_opts = NULL
     cdef void *cogr_ds = NULL
 
+    db = <const char *>GDALGetDriverShortName(cogr_driver)
+
+    # To avoid a circular import.
+    from fiona import meta
+
+    option_keys = set(key.upper() for key in options.keys())
+    creation_option_keys = option_keys & set(meta.dataset_creation_options(db.decode("utf-8")))
+
     for k, v in options.items():
-        k = k.upper().encode('utf-8')
-        if isinstance(v, bool):
-            v = ('ON' if v else 'OFF').encode('utf-8')
-        else:
-            v = str(v).encode('utf-8')
-        creation_opts = CSLAddNameValue(creation_opts, <const char *>k, <const char *>v)
+
+        if k.upper() in creation_option_keys:
+
+            kb = k.upper().encode('utf-8')
+
+            if isinstance(v, bool):
+                vb = ('ON' if v else 'OFF').encode('utf-8')
+            else:
+                vb = str(v).encode('utf-8')
+
+            creation_opts = CSLAddNameValue(creation_opts, <const char *>kb, <const char *>vb)
 
     try:
         return exc_wrap_pointer(GDALCreate(cogr_driver, path_c, 0, 0, 0, GDT_Unknown, creation_opts))
@@ -508,7 +523,7 @@ cdef _deleteOgrFeature(void *cogr_feature):
 
 def featureRT(feat, collection):
     # For testing purposes only, leaks the JSON data
-    feature = _guard_model_object(feat)
+    feature = decode_object(feat)
     cdef void *cogr_feature = OGRFeatureBuilder().build(feature, collection)
     cdef void *cogr_geometry = OGR_F_GetGeometryRef(cogr_feature)
     if cogr_geometry == NULL:
@@ -1131,40 +1146,48 @@ cdef class WritingSession(Session):
             name_b = collection.name.encode('utf-8')
             name_c = name_b
 
-            for k, v in kwargs.items():
+            # To avoid circular import.
+            from fiona import meta
 
-                if v is None:
-                    continue
+            kwarg_keys = set(key.upper() for key in kwargs.keys())
+            lyr_creation_option_keys = kwarg_keys & set(meta.layer_creation_options(collection.driver))
 
-                # 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
+            for k, v in kwargs.items():
 
-                k = k.upper().encode('utf-8')
+                if v is not None and k.upper() in lyr_creation_option_keys:
+                    kb = k.upper().encode('utf-8')
 
-                if isinstance(v, bool):
-                    v = ('ON' if v else 'OFF').encode('utf-8')
-                else:
-                    v = str(v).encode('utf-8')
-                options = CSLAddNameValue(options, <const char *>k, <const char *>v)
+                    if isinstance(v, bool):
+                        vb = ('ON' if v else 'OFF').encode('utf-8')
+                    else:
+                        vb = str(v).encode('utf-8')
+
+                    options = CSLAddNameValue(options, <const char *>kb, <const char *>vb)
 
             geometry_type = collection.schema.get("geometry", "Unknown")
+
             if not isinstance(geometry_type, str) and geometry_type is not None:
                 geometry_types = set(geometry_type)
+
                 if len(geometry_types) > 1:
                     geometry_type = "Unknown"
                 else:
                     geometry_type = geometry_types.pop()
+
             if geometry_type == "Any" or geometry_type is None:
                 geometry_type = "Unknown"
+
             geometry_code = geometry_type_code(geometry_type)
 
             try:
-                self.cogr_layer = exc_wrap_pointer(
-                    GDALDatasetCreateLayer(
-                        self.cogr_ds, name_c, cogr_srs,
-                        <OGRwkbGeometryType>geometry_code, options))
+                # In GDAL versions > 3.6.0 the following directive may
+                # suffice and we might be able to eliminate the import
+                # of fiona.meta in a future version of Fiona.
+                with Env(GDAL_VALIDATE_CREATION_OPTIONS="NO"):
+                    self.cogr_layer = exc_wrap_pointer(
+                        GDALDatasetCreateLayer(
+                            self.cogr_ds, name_c, cogr_srs,
+                            <OGRwkbGeometryType>geometry_code, options))
 
             except Exception as exc:
                 GDALClose(self.cogr_ds)
@@ -1273,19 +1296,28 @@ cdef class WritingSession(Session):
         log.debug("Writing started")
 
     def writerecs(self, records, collection):
-        """Writes buffered records to OGR."""
+        """Writes records to collection storage.
+
+        Parameters
+        ----------
+        records : Iterable
+            A stream of feature records.
+        collection : Collection
+            The collection in which feature records are stored.
+
+        Returns
+        -------
+        None
+
+        """
         cdef void *cogr_driver
         cdef void *cogr_feature
         cdef int features_in_transaction = 0
-
         cdef void *cogr_layer = self.cogr_layer
+
         if cogr_layer == NULL:
             raise ValueError("Null layer")
 
-        schema_geom_type = collection.schema['geometry']
-        cogr_driver = GDALGetDatasetDriver(self.cogr_ds)
-        driver_name = OGR_Dr_GetName(cogr_driver).decode("utf-8")
-
         valid_geom_types = collection._valid_geom_types
 
         def validate_geometry_type(record):
@@ -1295,6 +1327,7 @@ cdef class WritingSession(Session):
 
         transactions_supported = GDALDatasetTestCapability(self.cogr_ds, ODsCTransactions)
         log.debug("Transaction supported: {}".format(transactions_supported))
+
         if transactions_supported:
             log.debug("Starting transaction (initial)")
             result = GDALDatasetStartTransaction(self.cogr_ds, 0)
@@ -1304,13 +1337,7 @@ cdef class WritingSession(Session):
         schema_props_keys = set(collection.schema['properties'].keys())
 
         for _rec in records:
-            record = _guard_model_object(_rec)
-
-            # Check for optional elements
-            # if 'properties' not in _rec:
-            #     _rec['properties'] = {}
-            # if 'geometry' not in _rec:
-            #     _rec['geometry'] = None
+            record = decode_object(_rec)
 
             # Validate against collection's schema.
             if set(record.properties.keys()) != schema_props_keys:
@@ -1328,24 +1355,33 @@ cdef class WritingSession(Session):
 
             cogr_feature = OGRFeatureBuilder().build(record, collection)
             result = OGR_L_CreateFeature(cogr_layer, cogr_feature)
+
             if result != OGRERR_NONE:
                 msg = get_last_error_msg()
-                raise RuntimeError("GDAL Error: {msg} \n \n Failed to write record: "
-                                   "{record}".format(msg=msg, record=record))
+                raise RuntimeError(
+                    "GDAL Error: {msg}. Failed to write record: {record}".format(
+                        msg=msg, record=record
+                    )
+                )
 
             _deleteOgrFeature(cogr_feature)
 
             if transactions_supported:
                 features_in_transaction += 1
+
                 if features_in_transaction == DEFAULT_TRANSACTION_SIZE:
                     log.debug("Committing transaction (intermediate)")
                     result = GDALDatasetCommitTransaction(self.cogr_ds)
+
                     if result == OGRERR_FAILURE:
                         raise TransactionError("Failed to commit transaction")
+
                     log.debug("Starting transaction (intermediate)")
                     result = GDALDatasetStartTransaction(self.cogr_ds, 0)
+
                     if result == OGRERR_FAILURE:
                         raise TransactionError("Failed to start transaction")
+
                     features_in_transaction = 0
 
         if transactions_supported:
@@ -1475,7 +1511,7 @@ cdef class Iterator:
             OGR_L_SetSpatialFilterRect(
                 cogr_layer, bbox[0], bbox[1], bbox[2], bbox[3])
         elif mask:
-            mask_geom = _guard_model_object(mask)
+            mask_geom = decode_object(mask)
             cogr_geometry = OGRGeomBuilder().build(mask_geom)
             OGR_L_SetSpatialFilter(cogr_layer, cogr_geometry)
             OGR_G_DestroyGeometry(cogr_geometry)
@@ -2071,7 +2107,7 @@ def _get_metadata_item(driver, metadata_item):
 
     if get_gdal_version_tuple() < (2, ):
         return None
-    
+
     if driver is None:
         return None
 


=====================================
fiona/transform.py
=====================================
@@ -5,7 +5,7 @@ from warnings import warn
 from fiona._transform import _transform, _transform_geom
 from fiona.compat import DICT_TYPES
 from fiona.errors import FionaDeprecationWarning
-from fiona.model import _guard_model_object, Geometry
+from fiona.model import decode_object, Geometry
 
 
 def transform(src_crs, dst_crs, xs, ys):
@@ -108,7 +108,7 @@ def transform_geom(
         return _transform_geom(
             src_crs,
             dst_crs,
-            _guard_model_object(geom),
+            decode_object(geom),
             antimeridian_cutting,
             antimeridian_offset,
             precision,
@@ -117,7 +117,7 @@ def transform_geom(
         return _transform_geom(
             src_crs,
             dst_crs,
-            (_guard_model_object(g) for g in geom),
+            (decode_object(g) for g in geom),
             antimeridian_cutting,
             antimeridian_offset,
             precision,


=====================================
tests/test_collection.py
=====================================
@@ -1,11 +1,12 @@
 # Testing collections and workspaces
 
+from collections import OrderedDict
 import datetime
+import logging
 import os
 import random
-import sys
 import re
-from collections import OrderedDict
+import sys
 
 import pytest
 
@@ -1047,14 +1048,16 @@ def test_collection_zip_http():
 
 def test_encoding_option_warning(tmpdir, caplog):
     """There is no ENCODING creation option log warning for GeoJSON"""
-    fiona.Collection(
-        str(tmpdir.join("test.geojson")),
-        "w",
-        driver="GeoJSON",
-        crs="epsg:4326",
-        schema={"geometry": "Point", "properties": {"foo": "int"}},
-    )
-    assert not caplog.text
+    with caplog.at_level(logging.WARNING):
+        fiona.Collection(
+            str(tmpdir.join("test.geojson")),
+            "w",
+            driver="GeoJSON",
+            crs="EPSG:4326",
+            schema={"geometry": "Point", "properties": {"foo": "int"}},
+            encoding="bogus",
+        )
+        assert not caplog.text
 
 
 def test_closed_session_next(gdalenv, path_coutwildrnp_shp):


=====================================
tests/test_feature.py
=====================================
@@ -10,7 +10,6 @@ import pytest
 import fiona
 from fiona import collection
 from fiona.collection import Collection
-from fiona.errors import FionaDeprecationWarning
 from fiona.model import Feature
 from fiona.ogrext import featureRT
 
@@ -36,10 +35,9 @@ class TestPointRoundTrip(object):
             "geometry": {"type": "Point", "coordinates": (0.0, 0.0)},
             "properties": {"title": "foo"},
         }
-        with pytest.warns(FionaDeprecationWarning):
-            g = featureRT(f, self.c)
-            assert g.geometry.type == "Point"
-            assert g.geometry.coordinates == (0.0, 0.0)
+        g = featureRT(f, self.c)
+        assert g.geometry.type == "Point"
+        assert g.geometry.coordinates == (0.0, 0.0)
 
     def test_properties(self):
         f = Feature.from_dict(


=====================================
tests/test_fio_calc.py
=====================================
@@ -54,9 +54,10 @@ def test_bool_seq(feature_seq, runner):
 
 
 def test_existing_property(feature_seq, runner):
-    result = runner.invoke(main_group, ['calc', "AREA", "f.properties.AREA * 2"],
-                           feature_seq)
-    assert result.exit_code == 1
+    result = runner.invoke(
+        main_group, ["calc", "AREA", "f.properties.AREA * 2"], feature_seq
+    )
+    assert result.exit_code == 2
 
     result = runner.invoke(main_group, ['calc', "--overwrite", "AREA", "f.properties.AREA * 2"],
                            feature_seq)


=====================================
tests/test_geometry.py
=====================================
@@ -71,10 +71,9 @@ def test_geometry_collection_round_trip():
         ],
     }
 
-    with pytest.warns(DeprecationWarning):
-        result = geometryRT(geom)
-        assert len(result["geometries"]) == 2
-        assert [g["type"] for g in result["geometries"]] == ["Point", "LineString"]
+    result = geometryRT(geom)
+    assert len(result["geometries"]) == 2
+    assert [g["type"] for g in result["geometries"]] == ["Point", "LineString"]
 
 
 def test_point_wkb():


=====================================
tests/test_model.py
=====================================
@@ -130,6 +130,13 @@ def test_geometry__props():
     }
 
 
+def test_geometry_gi():
+    """Geometry __geo_interface__"""
+    gi = Geometry(coordinates=(0, 0), type="Point").__geo_interface__
+    assert gi["type"] == "Point"
+    assert gi["coordinates"] == (0, 0)
+
+
 def test_feature_no_geometry():
     """Feature has no attribute"""
     feat = Feature()
@@ -294,3 +301,16 @@ def test_decode_object_hook_fallback(o):
 def test_properties():
     """Property factory works"""
     assert Properties.from_dict(a=1, foo="bar")["a"] == 1
+
+
+def test_feature_gi():
+    """Feature __geo_interface__."""
+    gi = Feature(
+        id="foo",
+        geometry=Geometry(type="Point", coordinates=(0, 0)),
+        properties=Properties(a=1, foo="bar"),
+    )
+
+    assert gi["id"] == "foo"
+    assert gi["geometry"]["type"] == "Point"
+    assert gi["geometry"]["coordinates"] == (0, 0)


=====================================
tests/test_transform.py
=====================================
@@ -119,15 +119,14 @@ def test_transform_issue971():
             }
         ],
     }
-    with pytest.warns(FionaDeprecationWarning):
-        geom_transformed = transform.transform_geom(source_crs, dest_src, geom)
-        assert geom_transformed.geometries[0].coordinates[0] == pytest.approx(
-            (9.18427, 52.94630)
-        )
+    geom_transformed = transform.transform_geom(source_crs, dest_src, geom)
+    assert geom_transformed.geometries[0].coordinates[0] == pytest.approx(
+        (9.18427, 52.94630)
+    )
 
 
 def test_transform_geom_precision_deprecation():
-    """Get a deprecation warning in 1.9"""
+    """Get a precision deprecation warning in 1.9."""
     with pytest.warns(FionaDeprecationWarning):
         transform.transform_geom(
             "epsg:4326",



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/fiona/-/commit/216cbde590cd39c53b1153b80f665b9ada4754eb
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/20230122/64388144/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list