Skip to content

Commit

Permalink
Merge pull request #336 from Toblerity/no-brick-info
Browse files Browse the repository at this point in the history
Add --layer to $ fio info and $ fio load
  • Loading branch information
sgillies committed Apr 18, 2016
2 parents fba2c21 + c60ab3f commit 896e741
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ script:
- coverage run --source=fiona --omit='*.pxd,*.pyx,*/tests/*,*/docs/*,*/examples/*,*/benchmarks/*' -m nose --exclude test_filter_vsi --exclude test_geopackage

after_success:
- coveralls
- coveralls || echo "!! intermittent coveralls failure"

env:

Expand Down
8 changes: 6 additions & 2 deletions fiona/fio/cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,11 @@ def transformer(crs, feat):
'--sequence / --no-sequence', default=False,
help="Specify whether the input stream is a LF-delimited sequence of GeoJSON "
"features (the default) or a single GeoJSON feature collection.")
@click.option('--layer', metavar="INDEX|NAME", callback=options.cb_layer,
help="Load features into specified layer. Layers use zero-based numbering when "
"accessed by index.")
@click.pass_context
def load(ctx, output, driver, src_crs, dst_crs, sequence):
def load(ctx, output, driver, src_crs, dst_crs, sequence, layer):
"""Load features from JSON to a file in another format.
The input is a GeoJSON feature collection or optionally a sequence of
Expand Down Expand Up @@ -594,7 +597,8 @@ def feature_gen():
output, 'w',
driver=driver,
crs=dst_crs,
schema=schema) as dst:
schema=schema,
layer=layer) as dst:
dst.write(first)
dst.writerecords(source)

Expand Down
29 changes: 25 additions & 4 deletions fiona/fio/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import fiona
import fiona.crs
from fiona.fio import options


@click.command(short_help="Print information about the fio environment.")
Expand All @@ -35,9 +36,13 @@ def env(ctx, key):


# Info command.
@click.command(short_help="Print information about a dataset.")
@click.command()
# One or more files.
@click.argument('input', type=click.Path(exists=True))
@click.option('--layer', metavar="INDEX|NAME", callback=options.cb_layer,
help="Print information about a specific layer. The first layer "
"is used by default. Layers use zero-based numbering when "
"accessed by index.")
@indent_opt
# Options to pick out a single metadata item and print it as
# a string.
Expand All @@ -50,15 +55,31 @@ def env(ctx, key):
@click.option('--bounds', 'meta_member', flag_value='bounds',
help="Print the boundary coordinates "
"(left, bottom, right, top).")
@click.option('--name', 'meta_member', flag_value='name',
help="Print the datasource's name.")
@click.pass_context
def info(ctx, input, indent, meta_member):
def info(ctx, input, indent, meta_member, layer):

"""
Print information about a dataset.
When working with a multi-layer dataset the first layer is used by default.
Use the '--layer' option to select a different layer.
"""

verbosity = (ctx.obj and ctx.obj['verbosity']) or 2
logger = logging.getLogger('fio')
try:
with fiona.drivers(CPL_DEBUG=verbosity>2):
with fiona.open(input) as src:
with fiona.open(input, layer=layer) as src:
info = src.meta
info.update(bounds=src.bounds, count=len(src))
info.update(bounds=src.bounds, name=src.name)
try:
info.update(count=len(src))
except TypeError as e:
info.update(count=None)
logger.debug("Setting 'count' to None/null - layer does "
"not support counting")
proj4 = fiona.crs.to_string(src.crs)
if proj4.startswith('+init=epsg'):
proj4 = proj4.split('=')[1].upper()
Expand Down
9 changes: 9 additions & 0 deletions fiona/fio/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@

src_crs_opt = click.option('--src-crs', '--src_crs', help="Source CRS.")
dst_crs_opt = click.option('--dst-crs', '--dst_crs', help="Destination CRS.")


def cb_layer(ctx, param, value):
"""Let --layer be a name or index."""

if value is None or not value.isdigit():
return value
else:
return int(value)
38 changes: 0 additions & 38 deletions tests/test_cli.py

This file was deleted.

73 changes: 73 additions & 0 deletions tests/test_fio_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import json
from pkg_resources import iter_entry_points
import re

from click.testing import CliRunner

from fiona.fio.main import main_group


WILDSHP = 'tests/data/coutwildrnp.shp'


def test_info_json():
runner = CliRunner()
result = runner.invoke(main_group, ['info', WILDSHP])
assert result.exit_code == 0
assert '"count": 67' in result.output
assert '"crs": "EPSG:4326"' in result.output
assert '"driver": "ESRI Shapefile"' in result.output
assert '"name": "coutwildrnp"' in result.output


def test_info_count():
runner = CliRunner()
result = runner.invoke(main_group, ['info', '--count', WILDSHP])
assert result.exit_code == 0
assert result.output == "67\n"


def test_info_bounds():
runner = CliRunner()
result = runner.invoke(main_group, ['info', '--bounds', WILDSHP])
assert result.exit_code == 0
assert len(re.findall(r'\d*\.\d*', result.output)) == 4


def test_all_registered():
# Make sure all the subcommands are actually registered to the main CLI group
for ep in iter_entry_points('fiona.fio_commands'):
assert ep.name in main_group.commands


def _filter_info_warning(lines):
"""$ fio info can issue a RuntimeWarning, but click adds stderr to stdout
so we have to filter it out before decoding JSON lines."""
lines = list(filter(lambda x: 'RuntimeWarning' not in x, lines))
return lines


def test_info_no_count():
"""Make sure we can still get a `$ fio info` report on datasources that do
not support feature counting, AKA `len(collection)`.
"""
runner = CliRunner()
result = runner.invoke(main_group, ['info', 'tests/data/test_gpx.gpx'])
assert result.exit_code == 0
lines = _filter_info_warning(result.output.splitlines())
assert len(lines) == 1, "First line is warning & second is JSON. No more."
assert json.loads(lines[0])['count'] is None


def test_info_layer():
for layer in ('routes', '1'):
runner = CliRunner()
result = runner.invoke(main_group, [
'info',
'tests/data/test_gpx.gpx',
'--layer', layer])
print(result.output)
assert result.exit_code == 0
lines = _filter_info_warning(result.output.splitlines())
assert len(lines) == 1, "1st line is warning & 2nd is JSON - no more."
assert json.loads(lines[0])['name'] == 'routes'
37 changes: 37 additions & 0 deletions tests/test_fio_load.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import os
import shutil
import tempfile

from click.testing import CliRunner
Expand Down Expand Up @@ -119,3 +121,38 @@ def test_dst_crs_no_src(tmpdir=None):
with fiona.open(tmpfile) as src:
assert src.crs == {'init': 'epsg:32610'}
assert len(src) == len(feature_seq.splitlines())


def test_fio_load_layer():

tmpdir = tempfile.mkdtemp()
try:
feature = {
'type': 'Feature',
'properties': {'key': 'value'},
'geometry': {
'type': 'Point',
'coordinates': (5.0, 39.0)
}
}

sequence = os.linesep.join(map(json.dumps, [feature, feature]))

runner = CliRunner()
result = runner.invoke(main_group, [
'load',
tmpdir,
'--driver', 'ESRI Shapefile',
'--src-crs', 'EPSG:4236',
'--layer', 'test_layer',
'--sequence'],
input=sequence)
assert result.exit_code == 0

with fiona.open(tmpdir) as src:
assert len(src) == 2
assert src.name == 'test_layer'
assert src.schema['geometry'] == 'Point'

finally:
shutil.rmtree(tmpdir)

0 comments on commit 896e741

Please sign in to comment.