From 967c63a06ada600a621638d99ba23caf925daddd Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Wed, 3 May 2023 15:50:25 -0600 Subject: [PATCH] Test Python docstring in markdown files (#38) * Test Python docstring in markdown files And experiment with tested snippets * Use two CLI snippets * Appease flake8 --- conftest.py | 8 ++++++++ docs/expressions.md | 33 ++++++++++++++++++++++++++++----- mkdocs.yml | 3 +++ pyproject.toml | 1 + tests/test_cli.py | 34 +++++++++++++++++++++++++++++++--- tox.ini | 2 +- 6 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 conftest.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..aebd55a --- /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 diff --git a/docs/expressions.md b/docs/expressions.md index 24ca04c..396bbc8 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,17 +174,18 @@ size in meters. ```python >>> snuggs.eval('(set_precision (Point 0.001 0.001) :grid_size 500)') + ``` ## 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 @@ -170,5 +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" ``` 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..4bbdce7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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 @@ -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 @@ -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, ) 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 =