From 15ee99cdb4ae03cab1ddaecf597b43546e0852a9 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Wed, 3 May 2023 11:25:05 -0600 Subject: [PATCH 1/3] Test Python docstring in markdown files And experiment with tested snippets --- conftest.py | 8 ++++++++ docs/expressions.md | 31 ++++++++++++++++++++++++++++++- mkdocs.yml | 3 +++ pyproject.toml | 1 + tests/test_cli.py | 6 ++++-- tox.ini | 2 +- 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..3dd8564 --- /dev/null +++ b/conftest.py @@ -0,0 +1,8 @@ +import pytest + +from fio_planet import snuggs + + +@pytest.fixture(autouse=True) +def add_snuggs(doctest_namespace): + doctest_namespace["snuggs"] = snuggs \ No newline at end of file diff --git a/docs/expressions.md b/docs/expressions.md index 24ca04c..8e1c8e9 100644 --- a/docs/expressions.md +++ b/docs/expressions.md @@ -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 @@ -28,6 +36,7 @@ Note: the outer parentheses are not optional within `snuggs.eval()`. ```python >>> snuggs.eval('(bool 0)') False + ``` `range`: @@ -35,6 +44,7 @@ False ```python >>> snuggs.eval('(range 1 4)') range(1, 4) + ``` `list`: @@ -42,6 +52,7 @@ range(1, 4) ```python >>> snuggs.eval('(list (range 1 4))') [1, 2, 3] + ``` Values can be bound to names for use in expressions. @@ -49,6 +60,7 @@ 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 @@ -58,6 +70,7 @@ Here's an example of using `itertools.repeat()`. ```python >>> snuggs.eval('(list (repeat "*" times))', times=6) ['*', '*', '*', '*', '*', '*'] + ``` ## Shapely functions @@ -67,6 +80,7 @@ Here's an expression that evaluates to a Shapely Point instance. ```python >>> snuggs.eval('(Point 0 0)') + ``` The expression below evaluates to a MultiPoint instance. @@ -74,6 +88,7 @@ The expression below evaluates to a MultiPoint instance. ```python >>> snuggs.eval('(union (Point 0 0) (Point 1 1))') + ``` ## Functions specific to fio-planet @@ -91,6 +106,7 @@ geometries. >>> snuggs.eval('(list (dump (collect (Point 0 0) (Point 1 1))))') [, ] + ``` The `identity` function returns its single argument. @@ -98,6 +114,7 @@ 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`. @@ -105,6 +122,7 @@ 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` @@ -119,6 +137,7 @@ longitude and latitude degrees, by a given distance in meters. ```python >>> snuggs.eval('(buffer (Point 0 0) :distance 100)') + ``` The `area` and `length` of this polygon have units of square meter and meter. @@ -128,6 +147,7 @@ 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. @@ -135,6 +155,7 @@ 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`. @@ -144,6 +165,7 @@ There are more examples of this function under ```python >>> snuggs.eval('(simplify (buffer (Point 0 0) :distance 100) :tolerance 100)') + ``` The `set_precision` function snaps a geometry to a fixed precision grid with a @@ -152,6 +174,7 @@ size in meters. ```python >>> snuggs.eval('(set_precision (Point 0.001 0.001) :grid_size 500)') + ``` ## Feature and geometry context for expressions @@ -172,3 +195,9 @@ geometries using Shapely's `unary_union`. ```lisp unary_union c ``` + +## Snippet test + +```python +--8<-- "tests/test_cli.py:map" +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index cc3e22b..01ee515 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,9 @@ nav: - "Topics": 'topics' markdown_extensions: + - pymdownx.inlinehilite + - pymdownx.snippets: + dedent_subsections: True - pymdownx.highlight - pymdownx.superfences - mkdocs-click diff --git a/pyproject.toml b/pyproject.toml index 8c78ed5..0fc66df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,3 +53,4 @@ filterwarnings = [ "ignore:.*pkg_resources is deprecated as an API", "ignore:.*module \\'sre_constants\\' is deprecated", ] +doctest_optionflags = "NORMALIZE_WHITESPACE" \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 7dd00c2..44ef1dd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -25,16 +25,18 @@ def test_map_count(): with open("tests/data/trio.seq") as seq: data = seq.read() + # --8<-- [start:map] runner = CliRunner() result = runner.invoke( main_group, ["map", "centroid (buffer g 1.0)"], - input=data, + input=data, foo=42, ) + # --8<-- [end:map] + assert result.exit_code == 0 assert result.output.count('"type": "Point"') == 3 - @pytest.mark.parametrize("raw_opt", ["--raw", "-r"]) def test_reduce_area(raw_opt): """Reduce features to their (raw) area.""" diff --git a/tox.ini b/tox.ini index d003933..a75da61 100644 --- a/tox.ini +++ b/tox.ini @@ -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 = From 4338da63e4b404a48d2d704d7ee758e10a1773fc Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Wed, 3 May 2023 15:35:49 -0600 Subject: [PATCH 2/3] Use two CLI snippets --- docs/expressions.md | 14 ++++---------- tests/test_cli.py | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/docs/expressions.md b/docs/expressions.md index 8e1c8e9..396bbc8 100644 --- a/docs/expressions.md +++ b/docs/expressions.md @@ -181,11 +181,11 @@ size in meters. `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 @@ -193,11 +193,5 @@ 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" ``` - -## Snippet test - -```python ---8<-- "tests/test_cli.py:map" -``` \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py index 44ef1dd..4bbdce7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -25,18 +25,26 @@ def test_map_count(): with open("tests/data/trio.seq") as seq: data = seq.read() - # --8<-- [start:map] + # 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)"], - input=data, foo=42, + ["map", arg], # "centroid (buffer g 1.0)"], + input=data, ) - # --8<-- [end:map] assert result.exit_code == 0 assert result.output.count('"type": "Point"') == 3 + @pytest.mark.parametrize("raw_opt", ["--raw", "-r"]) def test_reduce_area(raw_opt): """Reduce features to their (raw) area.""" @@ -58,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 @@ -90,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, ) From 1d3af7c48a46e2df55f7b02ae0ec5cf79bab6313 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Wed, 3 May 2023 15:42:36 -0600 Subject: [PATCH 3/3] Appease flake8 --- conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conftest.py b/conftest.py index 3dd8564..aebd55a 100644 --- a/conftest.py +++ b/conftest.py @@ -5,4 +5,4 @@ @pytest.fixture(autouse=True) def add_snuggs(doctest_namespace): - doctest_namespace["snuggs"] = snuggs \ No newline at end of file + doctest_namespace["snuggs"] = snuggs