Skip to content

Commit

Permalink
Support passing file objects and BytesIO to fiona.open()
Browse files Browse the repository at this point in the history
  • Loading branch information
Sean C. Gillies committed Feb 3, 2018
1 parent 144a68b commit 91254d3
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 26 deletions.
8 changes: 8 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ Changes

All issue numbers are relative to https://github.com/Toblerity/Fiona/issues.

Next
----

New features:

- We've added a ``MemoryFile`` class which supports formatted in-memory
feature collections (#501).

1.8a1 (2017-11-06)
------------------

Expand Down
87 changes: 61 additions & 26 deletions fiona/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
writing modes) flush contents to disk when their ``with`` blocks end.
"""

from contextlib import contextmanager
import logging
import os
from six import string_types
Expand All @@ -70,6 +71,7 @@
from fiona._drivers import driver_count, GDALEnv
from fiona.drvsupport import supported_drivers
from fiona.compat import OrderedDict
from fiona.io import MemoryFile
from fiona.ogrext import _bounds, _listlayers, FIELD_TYPES_MAP, _remove
from fiona.ogrext import (
calc_gdal_version_num, get_gdal_version_num, get_gdal_release_name)
Expand All @@ -88,7 +90,7 @@


def open(
path,
fp,
mode='r',
driver=None,
schema=None,
Expand Down Expand Up @@ -153,32 +155,65 @@ def open(
'example.shp', enabled_drivers=['GeoJSON', 'ESRI Shapefile'])
"""
# Parse the vfs into a vsi and an archive path.
path, vsi, archive = parse_paths(path, vfs)
if mode in ('a', 'r'):
if archive:
if not os.path.exists(archive):
raise IOError("no such archive file: %r" % archive)
elif path != '-' and not os.path.exists(path):
raise IOError("no such file or directory: %r" % path)
c = Collection(path, mode, driver=driver, encoding=encoding,
layer=layer, vsi=vsi, archive=archive,
enabled_drivers=enabled_drivers, **kwargs)
elif mode == 'w':
if schema:
# Make an ordered dict of schema properties.
this_schema = schema.copy()
this_schema['properties'] = OrderedDict(schema['properties'])
else:
this_schema = None
c = Collection(path, mode, crs=crs, driver=driver, schema=this_schema,
encoding=encoding, layer=layer, vsi=vsi, archive=archive,
enabled_drivers=enabled_drivers, crs_wkt=crs_wkt,
**kwargs)
# Special case for file object argument.
if mode == 'r' and hasattr(fp, 'read'):

@contextmanager
def fp_reader(fp):
memfile = MemoryFile(fp.read())
dataset = memfile.open()
try:
yield dataset
finally:
dataset.close()
memfile.close()

return fp_reader(fp)

elif mode == 'w' and hasattr(fp, 'write'):

@contextmanager
def fp_writer(fp):
memfile = MemoryFile()
dataset = memfile.open(
driver=driver, crs=crs, schema=schema, layer=layer, **kwargs)
try:
yield dataset
finally:
dataset.close()
memfile.seek(0)
fp.write(memfile.read())
memfile.close()

return fp_writer(fp)

else:
raise ValueError(
"mode string must be one of 'r', 'w', or 'a', not %s" % mode)
return c
# Parse the vfs into a vsi and an archive path.
path, vsi, archive = parse_paths(fp, vfs)
if mode in ('a', 'r'):
if archive:
if not os.path.exists(archive):
raise IOError("no such archive file: %r" % archive)
elif path != '-' and not os.path.exists(path):
raise IOError("no such file or directory: %r" % path)
c = Collection(path, mode, driver=driver, encoding=encoding,
layer=layer, vsi=vsi, archive=archive,
enabled_drivers=enabled_drivers, **kwargs)
elif mode == 'w':
if schema:
# Make an ordered dict of schema properties.
this_schema = schema.copy()
this_schema['properties'] = OrderedDict(schema['properties'])
else:
this_schema = None
c = Collection(path, mode, crs=crs, driver=driver, schema=this_schema,
encoding=encoding, layer=layer, vsi=vsi, archive=archive,
enabled_drivers=enabled_drivers, crs_wkt=crs_wkt,
**kwargs)
else:
raise ValueError(
"mode string must be one of 'r', 'w', or 'a', not %s" % mode)
return c

collection = open

Expand Down
32 changes: 32 additions & 0 deletions tests/test_memoryfile.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests of MemoryFile and ZippedMemoryFile"""

from io import BytesIO
import pytest
import uuid

Expand Down Expand Up @@ -41,3 +42,34 @@ def test_write_memoryfile(profile_first_coutwildrnp_shp):
with MemoryFile(data) as memfile:
with memfile.open() as col:
assert len(col) == 1


def test_memoryfile_bytesio(path_coutwildrnp_json):
"""In-memory GeoJSON file can be read"""
data = open(path_coutwildrnp_json, 'rb').read()

with fiona.open(BytesIO(data)) as collection:
assert len(collection) == 67


def test_memoryfile_fileobj(path_coutwildrnp_json):
"""In-memory GeoJSON file can be read"""
with open(path_coutwildrnp_json, 'rb') as f:

with fiona.open(f) as collection:
assert len(collection) == 67


def test_write_memoryfile_(profile_first_coutwildrnp_shp):
"""In-memory Shapefile can be written"""
profile, first = profile_first_coutwildrnp_shp
profile['driver'] = 'GeoJSON'
with BytesIO() as fout:
with fiona.open(fout, 'w', **profile) as col:
col.write(first)
fout.seek(0)
data = fout.read()

with MemoryFile(data) as memfile:
with memfile.open() as col:
assert len(col) == 1

0 comments on commit 91254d3

Please sign in to comment.