From 66005fcbd3d5c53803a7ac59e8f902a780511ac7 Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:42:30 +0200 Subject: [PATCH 1/7] Add None guards to fix type errors and example code, --- docs/examples/animation/animated_buffer.py | 7 +++++-- docs/examples/api/adding_values_test.py | 1 + docs/examples/api/search_test.py | 2 ++ docs/examples/geometry/geometry.py | 4 ++++ docs/examples/helper.py | 1 + docs/examples/image_from_mapfile.py | 2 ++ 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/examples/animation/animated_buffer.py b/docs/examples/animation/animated_buffer.py index 8b964c0..3194715 100644 --- a/docs/examples/animation/animated_buffer.py +++ b/docs/examples/animation/animated_buffer.py @@ -17,12 +17,15 @@ def create_frame(mapfile, line, dist): # get the polygon layer pl = mappyfile.find(mapfile["layers"], "name", "polygon") + assert pl is not None # buffer the line dilated = line.buffer(dist, cap_style=3) # now set the FEATURES in the Mapfile to be the WKT from Shapely pl["features"][0]["wkt"] = "'%s'" % dilated.wkt # create an image from this Mapfile - return create_image("animation_%s" % str(dist), mapfile, format="gif") + return create_image( + "animation_%s" % str(dist), mapfile, output_folder="", format="gif" + ) def create_frames(mapfile): @@ -74,7 +77,7 @@ def create_animation(img_files): def main(): mf = "./docs/examples/animation/animated_buffer.map" - mapfile = mappyfile.load(mf) + mapfile = mappyfile.open(mf) img_files = create_frames(mapfile) create_animation(img_files) diff --git a/docs/examples/api/adding_values_test.py b/docs/examples/api/adding_values_test.py index 990360e..8646639 100644 --- a/docs/examples/api/adding_values_test.py +++ b/docs/examples/api/adding_values_test.py @@ -32,6 +32,7 @@ def test_class(): # START OF ADD CLASS EXAMPLE # find a layer using its name layer = mappyfile.find(mapfile["layers"], "name", "sea") + assert layer is not None new_class_string = """ CLASS diff --git a/docs/examples/api/search_test.py b/docs/examples/api/search_test.py index 3f82b05..8c08a96 100644 --- a/docs/examples/api/search_test.py +++ b/docs/examples/api/search_test.py @@ -8,10 +8,12 @@ def test(): # search for a layer by name layer = mappyfile.find(mapfile["layers"], "name", "sea") + assert layer is not None print(layer["name"]) # "sea" # search for all layers in a group for layer in mappyfile.findall(mapfile["layers"], "group", "my_group"): + assert layer is not None print(layer["name"]) # END OF API EXAMPLE diff --git a/docs/examples/geometry/geometry.py b/docs/examples/geometry/geometry.py index 479178f..2feb99e 100644 --- a/docs/examples/geometry/geometry.py +++ b/docs/examples/geometry/geometry.py @@ -12,10 +12,12 @@ def dilation(mapfile): line = LineString([(0, 0), (1, 1), (0, 2), (2, 2), (3, 1), (1, 0)]) ll = mappyfile.find(mapfile["layers"], "name", "line") + assert ll is not None ll["features"][0]["wkt"] = line.wkt dilated = line.buffer(0.5, cap_style=3) pl = mappyfile.find(mapfile["layers"], "name", "polygon") + assert pl is not None pl["features"][0]["wkt"] = dilated.wkt mapfile["extent"] = " ".join(map(str, dilated.buffer(0.8).bounds)) @@ -28,9 +30,11 @@ def erosion(mapfile, dilated): If we wanted to start from scratch we could simply reread it """ ll = mappyfile.find(mapfile["layers"], "name", "line") + assert ll is not None ll["status"] = "OFF" pl = mappyfile.find(mapfile["layers"], "name", "polygon") + assert pl is not None # make a deep copy of the polygon layer in the Map # so any modification are made to this layer only diff --git a/docs/examples/helper.py b/docs/examples/helper.py index c4242bb..7860baa 100644 --- a/docs/examples/helper.py +++ b/docs/examples/helper.py @@ -22,6 +22,7 @@ def _create_image_from_map(map_file, out_img, format): os.environ["PATH"] = DLL_LOCATION + ";" + os.environ["PATH"] p = Popen(params, stdout=PIPE, bufsize=1) + assert p.stdout is not None with p.stdout: for line in iter(p.stdout.readline, b""): print(line) diff --git a/docs/examples/image_from_mapfile.py b/docs/examples/image_from_mapfile.py index 75da75b..549da1c 100644 --- a/docs/examples/image_from_mapfile.py +++ b/docs/examples/image_from_mapfile.py @@ -18,6 +18,8 @@ def create_image_from_map(map_file, dll_location): logging.debug(" ".join(params)) p = Popen(params, stdout=PIPE, bufsize=1) + assert p.stdout is not None + with p.stdout: print(map_file) for line in iter(p.stdout.readline, b""): From d75d2e6a9bc6dd3792ff8b318ae9b6b7bf59b5c7 Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:43:24 +0200 Subject: [PATCH 2/7] Add type checks to fix mypy errors --- tests/test_validation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_validation.py b/tests/test_validation.py index ed751d3..1b0bed8 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -325,6 +325,7 @@ def test_deref(): schema_name = "cluster" validator = v.get_schema_validator(schema_name) jsn_schema = validator.schema + assert isinstance(jsn_schema, dict) print(json.dumps(jsn_schema, indent=4)) print(jsn_schema["properties"]["filter"]) @@ -345,11 +346,13 @@ def test_cached_schema(): schema_name = "cluster" validator = v.get_schema_validator(schema_name) jsn_schema = validator.schema + assert isinstance(jsn_schema, dict) assert list(jsn_schema["properties"]["filter"].keys())[0] == "$ref" # get the schame again validator = v.get_schema_validator(schema_name) jsn_schema = validator.schema + assert isinstance(jsn_schema, dict) assert list(jsn_schema["properties"]["filter"].keys())[0] == "$ref" From ee7d70f5059ba705f79f146b729ab39f7fb7d75c Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:43:59 +0200 Subject: [PATCH 3/7] Remove redundant variable --- tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_config.py b/tests/test_config.py index f844e8e..324cbaf 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -38,5 +38,5 @@ def test_parser_validation(): END END """ - with pytest.raises(UnexpectedToken) as e: + with pytest.raises(UnexpectedToken): p.parse(config_text_bad2) From ada364a33e32348a77e05f231a0a52ca69f7da20 Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:44:25 +0200 Subject: [PATCH 4/7] Whitespace fix --- misc/docs_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/docs_parser.py b/misc/docs_parser.py index 83e7f78..6547e04 100644 --- a/misc/docs_parser.py +++ b/misc/docs_parser.py @@ -4,7 +4,7 @@ https://dzone.com/articles/a-brief-tutorial-on-parsing-restructuredtext-rest https://github.com/eliben/code-for-blog/blob/master/2017/parsing-rst/rst-link-check.py -The root cause of the problem is indeed the fact that registering new roles/directives affects docutils +The root cause of the problem is indeed the fact that registering new roles/directives affects docutils globally. I don't think this can be easily solved. https://github.com/sphinx-doc/sphinx/issues/2799 From fc2eb171d32424dbce74879fa0f3e496e727f45b Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:56:14 +0200 Subject: [PATCH 5/7] mypy type fixes --- mappyfile/dictutils.py | 2 +- mappyfile/ordereddict.py | 10 ++++++---- mappyfile/validator.py | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mappyfile/dictutils.py b/mappyfile/dictutils.py index bc6e1ee..6d15160 100644 --- a/mappyfile/dictutils.py +++ b/mappyfile/dictutils.py @@ -195,7 +195,7 @@ def findunique(lst, key): ) -def findkey(d: dict, *keys: list[Any]) -> dict: +def findkey(d: dict, *keys: Any) -> dict: """ Get a value from a dictionary based on a list of keys and/or list indexes. diff --git a/mappyfile/ordereddict.py b/mappyfile/ordereddict.py index ab27b0d..7f60198 100644 --- a/mappyfile/ordereddict.py +++ b/mappyfile/ordereddict.py @@ -36,6 +36,7 @@ # ================================================================= from collections import OrderedDict +from typing import Any import copy from mappyfile.tokens import OBJECT_LIST_KEYS import json @@ -138,10 +139,11 @@ def setdefault(self, key, *args, **kwargs): # pylint: disable=protected-access return super().setdefault(self.__class__._k(key), *args, **kwargs) - def update(self, e=None, **f): - if e is not None: - super().update(self.__class__(CaseInsensitiveOrderedDict, e)) - super().update(self.__class__(CaseInsensitiveOrderedDict, **f)) + def update(self, *args: Any, **kwargs: Any) -> None: + for arg in args: + super().update(self.__class__(CaseInsensitiveOrderedDict, arg)) + if kwargs: + super().update(self.__class__(CaseInsensitiveOrderedDict, **kwargs)) def _convert_keys(self): for k in list(self.keys()): diff --git a/mappyfile/validator.py b/mappyfile/validator.py index aa3d132..015a75d 100644 --- a/mappyfile/validator.py +++ b/mappyfile/validator.py @@ -88,6 +88,7 @@ def get_schema_file(self, schema_name: str) -> str: def get_json_from_file(self, schema_name: str): if schema_name not in self.schemas: schema_file = self.get_schema_file(schema_name) + with open(schema_file, encoding="utf-8") as f: try: jsn_schema = json.load(f) @@ -197,7 +198,7 @@ def convert_lowercase(self, x: Any) -> Any: return x def create_message( - self, rootdict: dict, path: str, error, add_comments: bool + self, rootdict: dict, path: list[Any], error, add_comments: bool ) -> dict: """ Add a validation comment to the dictionary From 5192266b5f3cd998ddcc50c1d357d75d226afc82 Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 11:57:03 +0200 Subject: [PATCH 6/7] Add type checks with --check-untyped-def flag --- .github/workflows/main.yml | 2 +- run_local.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd56775..20117c6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: flake8 . - name: Check types with mypy run: | - mypy mappyfile tests docs/examples + mypy mappyfile tests docs/examples --check-untyped-defs - name: Command line tests run: | mappyfile schema mapfile-schema.json diff --git a/run_local.ps1 b/run_local.ps1 index 226d969..b20ecb6 100644 --- a/run_local.ps1 +++ b/run_local.ps1 @@ -12,7 +12,7 @@ flake8 . # mypy --install-types # to check within functions missing types # mypy mappyfile tests --check-untyped-defs -mypy mappyfile tests +mypy mappyfile tests docs/examples --check-untyped-defs pytest --doctest-modules # pytest ./tests \ No newline at end of file From 2f106ea2052e0cb1937238ac6b5223f057ca1c9b Mon Sep 17 00:00:00 2001 From: sethg Date: Wed, 30 Jul 2025 12:10:00 +0200 Subject: [PATCH 7/7] Avoid os.startfile as it is Windows only --- docs/examples/helper.py | 1 - docs/examples/image_from_mapfile.py | 13 ++++++++++++- docs/scripts/class_diagrams.py | 2 -- tests/test_validation.py | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/examples/helper.py b/docs/examples/helper.py index 7860baa..2f113ea 100644 --- a/docs/examples/helper.py +++ b/docs/examples/helper.py @@ -29,5 +29,4 @@ def _create_image_from_map(map_file, out_img, format): p.wait() # wait for the subprocess to exit - # os.startfile(out_img) return out_img diff --git a/docs/examples/image_from_mapfile.py b/docs/examples/image_from_mapfile.py index 549da1c..5f2c78b 100644 --- a/docs/examples/image_from_mapfile.py +++ b/docs/examples/image_from_mapfile.py @@ -1,9 +1,20 @@ +import subprocess from subprocess import Popen, PIPE import tempfile import logging +import sys import os +def open_file(path: str): + if sys.platform == "win32": + os.startfile(path) # subprocess not needed here + elif sys.platform == "darwin": + subprocess.run(["open", path], check=True) + else: + subprocess.run(["xdg-open", path], check=True) + + def create_image_from_map(map_file, dll_location): of = tempfile.NamedTemporaryFile(delete=False, suffix=".png", prefix="tmp_") of.close() @@ -27,7 +38,7 @@ def create_image_from_map(map_file, dll_location): p.wait() # wait for the subprocess to exit - os.startfile(of.name) + open_file(of.name) if __name__ == "__main__": diff --git a/docs/scripts/class_diagrams.py b/docs/scripts/class_diagrams.py index 65f91fc..1c30271 100644 --- a/docs/scripts/class_diagrams.py +++ b/docs/scripts/class_diagrams.py @@ -19,7 +19,6 @@ python class_diagrams.py """ -import os import pydot FONT = "Lucida Sans" @@ -59,7 +58,6 @@ def save_file(graph, fn): filename = "%s.png" % fn graph.write_png(filename) graph.write("%s.dot" % fn) - os.startfile(filename) def layer_children(): diff --git a/tests/test_validation.py b/tests/test_validation.py index 1b0bed8..1723fca 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -61,7 +61,8 @@ def _create_image_from_map(map_file, out_img, format): return None else: logging.info("Created %s", out_img) - os.startfile(out_img) + # os.startfile is only available on Windows + # os.startfile(out_img) return out_img