Skip to content

Commit

Permalink
Test Python docstring in markdown files (#38)
Browse files Browse the repository at this point in the history
* Test Python docstring in markdown files

And experiment with tested snippets

* Use two CLI snippets

* Appease flake8
  • Loading branch information
sgillies authored May 3, 2023
1 parent fc0a73d commit 967c63a
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 9 deletions.
8 changes: 8 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import pytest

from fio_planet import snuggs


@pytest.fixture(autouse=True)
def add_snuggs(doctest_namespace):
doctest_namespace["snuggs"] = snuggs
33 changes: 28 additions & 5 deletions docs/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ includes:
Expressions are evaluated by `fio_planet.features.snuggs.eval()`. Let's look at
some examples using that function.

Note: the outer parentheses are not optional within `snuggs.eval()`.
!!! note

The outer parentheses are not optional within `snuggs.eval()`.

!!! note

`snuggs.eval()` does not use Python's builtin `eval()` but isn't intended
to be a secure computing environment. Expressions which access the
computer's filesystem and create new processes are possible.

## Builtin Python functions

Expand All @@ -28,27 +36,31 @@ Note: the outer parentheses are not optional within `snuggs.eval()`.
```python
>>> snuggs.eval('(bool 0)')
False

```

`range`:

```python
>>> snuggs.eval('(range 1 4)')
range(1, 4)

```

`list`:

```python
>>> snuggs.eval('(list (range 1 4))')
[1, 2, 3]

```

Values can be bound to names for use in expressions.

```python
>>> snuggs.eval('(list (range start stop))', start=0, stop=5)
[0, 1, 2, 3, 4]

```

## Itertools functions
Expand All @@ -58,6 +70,7 @@ Here's an example of using `itertools.repeat()`.
```python
>>> snuggs.eval('(list (repeat "*" times))', times=6)
['*', '*', '*', '*', '*', '*']

```

## Shapely functions
Expand All @@ -67,13 +80,15 @@ Here's an expression that evaluates to a Shapely Point instance.
```python
>>> snuggs.eval('(Point 0 0)')
<POINT (0 0)>

```

The expression below evaluates to a MultiPoint instance.

```python
>>> snuggs.eval('(union (Point 0 0) (Point 1 1))')
<MULTIPOINT (0 0, 1 1)>

```

## Functions specific to fio-planet
Expand All @@ -91,20 +106,23 @@ geometries.
<GEOMETRYCOLLECTION (POINT (0 0), POINT (1 1))>
>>> snuggs.eval('(list (dump (collect (Point 0 0) (Point 1 1))))')
[<POINT (0 0)>, <POINT (1 1)>]

```

The `identity` function returns its single argument.

```python
>>> snuggs.eval('(identity 42)')
42

```

To count the number of vertices in a geometry, use `vertex_count`.

```python
>>> snuggs.eval('(vertex_count (Point 0 0))')
1

```

The `area`, `buffer`, `distance`, `length`, `simplify`, and `set_precision`
Expand All @@ -119,6 +137,7 @@ longitude and latitude degrees, by a given distance in meters.
```python
>>> snuggs.eval('(buffer (Point 0 0) :distance 100)')
<POLYGON ((0.001 0, 0.001 0, 0.001 0, 0.001 0, 0.001 -0.001, 0.001 -0.001, 0...>

```

The `area` and `length` of this polygon have units of square meter and meter.
Expand All @@ -128,13 +147,15 @@ The `area` and `length` of this polygon have units of square meter and meter.
31214.451487413342
>>> snuggs.eval('(length (buffer (Point 0 0) :distance 100))')
627.3096977558143

```

The `distance` between two geometries is in meters.

```python
>>> snuggs.eval('(distance (Point 0 0) (Point 0.1 0.1))')
15995.164946207413

```

A geometry can be simplified to a tolerance value in meters using `simplify`.
Expand All @@ -144,6 +165,7 @@ There are more examples of this function under
```python
>>> snuggs.eval('(simplify (buffer (Point 0 0) :distance 100) :tolerance 100)')
<POLYGON ((0.001 0, 0 -0.001, -0.001 0, 0 0.001, 0.001 0))>

```

The `set_precision` function snaps a geometry to a fixed precision grid with a
Expand All @@ -152,23 +174,24 @@ size in meters.
```python
>>> snuggs.eval('(set_precision (Point 0.001 0.001) :grid_size 500)')
<POINT (0 0)>

```

## Feature and geometry context for expressions

`fio-filter` and `fio-map` evaluate expressions in the context of a GeoJSON
feature and its geometry attribute. These are named `f` and `g`. For example,
here is an expression that tests whether the input feature is within 50 meters
of the given point.
here is an expression that tests whether the input feature is within 62.5
kilometers of the given point.

```lisp
<= (distance g (Point -105.0 39.753056)) 50.0
--8<-- "tests/test_cli.py:filter"
```

`fio-reduce` evaluates expressions in the context of the sequence of all input
geometries, named `c`. For example, this expression dissolves input
geometries using Shapely's `unary_union`.

```lisp
unary_union c
--8<-- "tests/test_cli.py:reduce"
```
3 changes: 3 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ nav:
- "Topics": 'topics'

markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets:
dedent_subsections: True
- pymdownx.highlight
- pymdownx.superfences
- mkdocs-click
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ filterwarnings = [
"ignore:.*pkg_resources is deprecated as an API",
"ignore:.*module \\'sre_constants\\' is deprecated",
]
doctest_optionflags = "NORMALIZE_WHITESPACE"
34 changes: 31 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,22 @@ def test_map_count():
with open("tests/data/trio.seq") as seq:
data = seq.read()

# Define our map arg using a mkdocs snippet.
arg = """
--8<-- [start:map]
centroid (buffer g 1.0)
--8<-- [end:map]
""".splitlines()[
2
].strip()

runner = CliRunner()
result = runner.invoke(
main_group,
["map", "centroid (buffer g 1.0)"],
["map", arg], # "centroid (buffer g 1.0)"],
input=data,
)

assert result.exit_code == 0
assert result.output.count('"type": "Point"') == 3

Expand All @@ -56,8 +66,17 @@ def test_reduce_union():
with open("tests/data/trio.seq") as seq:
data = seq.read()

# Define our reduce command using a mkdocs snippet.
arg = """
--8<-- [start:reduce]
unary_union c
--8<-- [end:reduce]
""".splitlines()[
2
].strip()

runner = CliRunner()
result = runner.invoke(main_group, ["reduce", "unary_union c"], input=data)
result = runner.invoke(main_group, ["reduce", arg], input=data)
assert result.exit_code == 0
assert result.output.count('"type": "Polygon"') == 1
assert result.output.count('"type": "LineString"') == 1
Expand Down Expand Up @@ -88,10 +107,19 @@ def test_filter():
with open("tests/data/trio.seq") as seq:
data = seq.read()

# Define our reduce command using a mkdocs snippet.
arg = """
--8<-- [start:filter]
< (distance g (Point 4 43)) 62.5E3
--8<-- [end:filter]
""".splitlines()[
2
].strip()

runner = CliRunner()
result = runner.invoke(
main_group,
["filter", "< (distance g (Point 4 43)) 62.5E3"],
["filter", arg],
input=data,
catch_exceptions=False,
)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ envlist =
deps =
pytest-cov
commands =
python -m pytest -v --cov fio_planet --cov-report term-missing --pdb
python -m pytest -v --cov fio_planet --cov-report term-missing --doctest-glob="*.md"

[gh-actions]
python =
Expand Down

0 comments on commit 967c63a

Please sign in to comment.