From 13dec6c72e6b61d45ef7bee8828c1355a78fd649 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 15 Sep 2024 18:04:24 +0100 Subject: [PATCH 01/15] handle verbosity in output --- fastkml/base.py | 1 + fastkml/enums.py | 2 +- fastkml/geometry.py | 2 + fastkml/helpers.py | 113 ++++++++++++++++++++++++++++++++++++-------- fastkml/registry.py | 2 + pyproject.toml | 1 - 6 files changed, 99 insertions(+), 22 deletions(-) diff --git a/fastkml/base.py b/fastkml/base.py index 15c00da7..c44f1549 100644 --- a/fastkml/base.py +++ b/fastkml/base.py @@ -152,6 +152,7 @@ def etree_element( node_name=item.node_name, precision=precision, verbosity=verbosity, + default=item.default, ) return element diff --git a/fastkml/enums.py b/fastkml/enums.py index 407efed5..5c13befe 100644 --- a/fastkml/enums.py +++ b/fastkml/enums.py @@ -77,7 +77,7 @@ def _missing_(cls, value: object) -> "RelaxedEnum": class Verbosity(Enum): """Enum to represent the different verbosity levels.""" - quiet = 0 + terse = 0 normal = 1 verbose = 2 diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 84160499..4f6267d8 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -138,6 +138,7 @@ def coordinates_subelement( node_name: str, # noqa: ARG001 precision: Optional[int], verbosity: Optional[Verbosity], # noqa: ARG001 + default: Any, # noqa: ARG001 ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -150,6 +151,7 @@ def coordinates_subelement( node_name (str): The name of the subelement to create. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Any): The default value of the attribute (unused). Returns: ------- diff --git a/fastkml/helpers.py b/fastkml/helpers.py index 193365ec..06c56f62 100644 --- a/fastkml/helpers.py +++ b/fastkml/helpers.py @@ -17,6 +17,7 @@ import logging from enum import Enum +from typing import Any from typing import Dict from typing import List from typing import Optional @@ -79,6 +80,35 @@ def handle_error( logger.warning("%s, %s", error, msg) +def get_value( + obj: _XMLObject, + *, + attr_name: str, + verbosity: Optional[Verbosity], + default: Optional[Any] = None, +) -> Optional[Any]: + """ + Get the value of an attribute from an object. + + If the verbosity is set to `Verbosity.terse`, the function returns `None` if the + attribute value is equal to the default value. If the verbosity is set to + `Verbosity.verbose`, the function returns the default value if the attribute value + is `None`. + + Args: + ---- + obj (_XMLObject): The object to get the attribute value from. + attr_name (str): The name of the attribute to retrieve. + verbosity (Optional[Verbosity]): The verbosity. + default (Optional[Any]): The default value. + + """ + value = getattr(obj, attr_name, None) + if value is None and default is not None and verbosity == Verbosity.verbose: + return default + return None if value == default and verbosity == Verbosity.terse else value + + def node_text( obj: _XMLObject, *, @@ -87,6 +117,7 @@ def node_text( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[str] = None, ) -> None: """ Set the text of an XML element based on the attribute value in the given object. @@ -105,6 +136,8 @@ def node_text( The precision to use when converting numeric values to text (unused). verbosity : Optional[Verbosity] The verbosity level for logging (unused). + default : Optional[str] + The default value for the attribute. Returns ------- @@ -112,8 +145,13 @@ def node_text( This function does not return anything. """ - if getattr(obj, attr_name, None): - element.text = getattr(obj, attr_name) + if value := get_value( + obj, + attr_name=attr_name, + verbosity=verbosity, + default=default, + ): + element.text = value def text_subelement( @@ -124,6 +162,7 @@ def text_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[str] = None, ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -136,18 +175,24 @@ def text_subelement( node_name (str): The name of the subelement to create. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Optional[str]): The default value for the attribute. Returns: ------- None """ - if getattr(obj, attr_name, None): + if value := get_value( + obj, + attr_name=attr_name, + verbosity=verbosity, + default=default, + ): subelement = config.etree.SubElement( element, f"{obj.ns}{node_name}", ) - subelement.text = getattr(obj, attr_name) + subelement.text = value def text_attribute( @@ -158,6 +203,7 @@ def text_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[str] = None, ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -170,14 +216,20 @@ def text_attribute( node_name (str): The name of the attribute to be set. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Optional[str]): The default value for the attribute. Returns: ------- None """ - if getattr(obj, attr_name, None): - element.set(node_name, getattr(obj, attr_name)) + if value := get_value( + obj, + attr_name=attr_name, + verbosity=verbosity, + default=default, + ): + element.set(node_name, value) def bool_subelement( @@ -188,6 +240,7 @@ def bool_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[bool] = None, ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -200,18 +253,20 @@ def bool_subelement( node_name (str): The name of the subelement to create. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Optional[bool]): The default value for the attribute. Returns: ------- None """ - if getattr(obj, attr_name, None) is not None: + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: subelement = config.etree.SubElement( element, f"{obj.ns}{node_name}", ) - subelement.text = str(int(getattr(obj, attr_name))) + subelement.text = str(int(value)) def int_subelement( @@ -222,6 +277,7 @@ def int_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[int] = None, ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -234,18 +290,20 @@ def int_subelement( node_name (str): The name of the subelement to create. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Optional[int]): The default value for the attribute. Returns: ------- None: This function does not return anything. """ - if getattr(obj, attr_name, None) is not None: + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: subelement = config.etree.SubElement( element, f"{obj.ns}{node_name}", ) - subelement.text = str(getattr(obj, attr_name)) + subelement.text = str(value) def int_attribute( @@ -256,6 +314,7 @@ def int_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[int] = None, ) -> None: """ Set the value of an attribute. @@ -268,14 +327,16 @@ def int_attribute( node_name (str): The name of the attribute to be set. precision (Optional[int]): The precision of the attribute value. verbosity (Optional[Verbosity]): The verbosity level. + default (Optional[int]): The default value for the attribute. Returns: ------- None: This function does not return anything. """ - if getattr(obj, attr_name, None) is not None: - element.set(node_name, str(getattr(obj, attr_name))) + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: + element.set(node_name, str(value)) def float_subelement( @@ -286,14 +347,16 @@ def float_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[float] = None, ) -> None: """Set the value of an attribute from a subelement with a text node.""" - if getattr(obj, attr_name, None) is not None: + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: subelement = config.etree.SubElement( element, f"{obj.ns}{node_name}", ) - subelement.text = str(getattr(obj, attr_name)) + subelement.text = str(value) def float_attribute( @@ -304,10 +367,12 @@ def float_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[float] = None, ) -> None: """Set the value of an attribute.""" - if getattr(obj, attr_name, None) is not None: - element.set(node_name, str(getattr(obj, attr_name))) + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: + element.set(node_name, str(value)) def enum_subelement( @@ -318,14 +383,16 @@ def enum_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[Enum] = None, ) -> None: """Set the value of an attribute from a subelement with a text node.""" - if getattr(obj, attr_name, None): + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: subelement = config.etree.SubElement( element, f"{obj.ns}{node_name}", ) - subelement.text = getattr(obj, attr_name).value + subelement.text = value.value def enum_attribute( @@ -336,10 +403,12 @@ def enum_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[Enum] = None, ) -> None: """Set the value of an attribute.""" - if getattr(obj, attr_name, None): - element.set(node_name, getattr(obj, attr_name).value) + value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) + if value is not None: + element.set(node_name, value.value) def xml_subelement( @@ -350,6 +419,7 @@ def xml_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[_XMLObject] = None, ) -> None: """ Add a subelement to an XML element based on the value of an attribute of an object. @@ -362,6 +432,7 @@ def xml_subelement( node_name (str): The name of the XML node for the subelement (unused). precision (Optional[int]): The precision for formatting numerical values. verbosity (Optional[Verbosity]): The verbosity level for the subelement. + default (Optional[_XMLObject]): The default value for the attribute (unused). Returns: ------- @@ -385,6 +456,7 @@ def xml_subelement_list( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Optional[List[_XMLObject]] = None, ) -> None: """ Add subelements to an XML element based on a list attribute of an object. @@ -397,6 +469,7 @@ def xml_subelement_list( node_name (str): The name of the XML node for each subelement (unused). precision (Optional[int]): The precision for floating-point values. verbosity (Optional[Verbosity]): The verbosity level for the XML output. + default (Optional[List[_XMLObject]]): The default value for the attribute. Returns: ------- diff --git a/fastkml/registry.py b/fastkml/registry.py index d80aaff0..94aaea7a 100644 --- a/fastkml/registry.py +++ b/fastkml/registry.py @@ -68,6 +68,7 @@ def __call__( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Any, ) -> None: ... @@ -81,6 +82,7 @@ class RegistryItem: get_kwarg: GetKWArgs set_element: SetElement node_name: str + default: Any = None class Registry: diff --git a/pyproject.toml b/pyproject.toml index f194d40b..0924b2f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,6 @@ ignore = [ ".*", "examples/*", "mutmut_config.py", - "test-requirements.txt", "tox.ini", ] From a9233f147dfd560df2a384184b0346f18824dfbe Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Sep 2024 16:35:43 +0100 Subject: [PATCH 02/15] add tests for geometry altitudeMode verbosity --- fastkml/geometry.py | 1 + tests/geometries/geometry_test.py | 79 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 4f6267d8..8ff8387c 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -410,6 +410,7 @@ def __repr__(self) -> str: node_name="altitudeMode", get_kwarg=subelement_enum_kwarg, set_element=enum_subelement, + default=AltitudeMode.clamp_to_ground, ), ) diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index 0d47a3f5..6613ed29 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -19,6 +19,7 @@ from pygeoif import geometry as geo from fastkml import exceptions +from fastkml.enums import Verbosity from fastkml.geometry import AltitudeMode from fastkml.geometry import LinearRing from fastkml.geometry import LineString @@ -331,6 +332,84 @@ def test_to_string_with_args(self) -> None: assert "altitudeMode>relativeToGround<" in g.to_string() assert "tessellate>1<" in g.to_string() + def test_to_string_terse_default(self) -> None: + """Test that with terse verbosity, only the necessary elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + id="my-id", + altitude_mode=AltitudeMode.clamp_to_ground, + ) + + xml = g.to_string(verbosity=Verbosity.terse) + + assert "altitudeMode" not in xml + assert "clampToGround" not in xml + + def test_to_string_terse(self) -> None: + """Test that with terse verbosity, only the necessary elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + id="my-id", + altitude_mode=AltitudeMode.relative_to_ground, + ) + + xml = g.to_string(verbosity=Verbosity.terse) + + assert "altitudeMode>relativeToGround<" in xml + + def test_to_string_terse_unset(self) -> None: + """Test that with terse verbosity, only the necessary elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + id="my-id", + ) + + xml = g.to_string(verbosity=Verbosity.terse) + + assert "altitudeMode" not in xml + assert "clampToGround" not in xml + + def test_to_string_verbose(self) -> None: + """Test that with verbose verbosity, all elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + altitude_mode=AltitudeMode.relative_to_ground, + id="my-id", + ) + + xml = g.to_string(verbosity=Verbosity.verbose) + + assert "altitudeMode>relativeToGround<" in xml + + def test_to_string_verbose_default_set(self) -> None: + """Test that with verbose verbosity, all elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + altitude_mode=AltitudeMode.clamp_to_ground, + id="my-id", + ) + + xml = g.to_string(verbosity=Verbosity.verbose) + + assert "altitudeMode>clampToGround<" in xml + + def test_to_string_verbose_default(self) -> None: + """Test that with verbose verbosity, all elements are included.""" + g = _Geometry( + ns="{http://www.opengis.net/kml/2.3}", + target_id="target_id", + id="my-id", + ) + + xml = g.to_string(verbosity=Verbosity.verbose) + + assert "altitudeMode>clampToGround<" in xml + def test_from_string(self) -> None: """Test the from_string method.""" g = _Geometry.class_from_string( From a96ecf2fd894e856a985c5833202a4fef778d64b Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Sep 2024 16:48:22 +0100 Subject: [PATCH 03/15] omit default value for default arg if not needed --- fastkml/helpers.py | 26 +++++++++++++------------- fastkml/kml.py | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/fastkml/helpers.py b/fastkml/helpers.py index 06c56f62..ee35ad19 100644 --- a/fastkml/helpers.py +++ b/fastkml/helpers.py @@ -85,7 +85,7 @@ def get_value( *, attr_name: str, verbosity: Optional[Verbosity], - default: Optional[Any] = None, + default: Optional[Any], ) -> Optional[Any]: """ Get the value of an attribute from an object. @@ -117,7 +117,7 @@ def node_text( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[str] = None, + default: Optional[str], ) -> None: """ Set the text of an XML element based on the attribute value in the given object. @@ -162,7 +162,7 @@ def text_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[str] = None, + default: Optional[str], ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -203,7 +203,7 @@ def text_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[str] = None, + default: Optional[str], ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -240,7 +240,7 @@ def bool_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[bool] = None, + default: Optional[bool], ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -277,7 +277,7 @@ def int_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[int] = None, + default: Optional[int], ) -> None: """ Set the value of an attribute from a subelement with a text node. @@ -314,7 +314,7 @@ def int_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[int] = None, + default: Optional[int], ) -> None: """ Set the value of an attribute. @@ -347,7 +347,7 @@ def float_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[float] = None, + default: Optional[float], ) -> None: """Set the value of an attribute from a subelement with a text node.""" value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) @@ -367,7 +367,7 @@ def float_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[float] = None, + default: Optional[float], ) -> None: """Set the value of an attribute.""" value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) @@ -383,7 +383,7 @@ def enum_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[Enum] = None, + default: Optional[Enum], ) -> None: """Set the value of an attribute from a subelement with a text node.""" value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) @@ -403,7 +403,7 @@ def enum_attribute( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[Enum] = None, + default: Optional[Enum], ) -> None: """Set the value of an attribute.""" value = get_value(obj, attr_name=attr_name, verbosity=verbosity, default=default) @@ -419,7 +419,7 @@ def xml_subelement( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[_XMLObject] = None, + default: Optional[_XMLObject], ) -> None: """ Add a subelement to an XML element based on the value of an attribute of an object. @@ -456,7 +456,7 @@ def xml_subelement_list( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], - default: Optional[List[_XMLObject]] = None, + default: Optional[List[_XMLObject]], ) -> None: """ Add subelements to an XML element based on a list attribute of an object. diff --git a/fastkml/kml.py b/fastkml/kml.py index 05446dea..00272128 100644 --- a/fastkml/kml.py +++ b/fastkml/kml.py @@ -144,6 +144,7 @@ def etree_element( node_name="", precision=precision, verbosity=verbosity, + default=None, ) return cast(Element, root) From 3f5b3723c495a7e2b35ae2a6a89b92fbf47d2270 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Sep 2024 18:20:17 +0100 Subject: [PATCH 04/15] add tests for default values for extrude and tesselate for geometry --- fastkml/geometry.py | 2 ++ tests/geometries/geometry_test.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 8ff8387c..9163f9fd 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -388,6 +388,7 @@ def __repr__(self) -> str: node_name="extrude", get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=False, ), ) registry.register( @@ -399,6 +400,7 @@ def __repr__(self) -> str: node_name="tessellate", get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=False, ), ) registry.register( diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index 6613ed29..98297693 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -339,12 +339,16 @@ def test_to_string_terse_default(self) -> None: target_id="target_id", id="my-id", altitude_mode=AltitudeMode.clamp_to_ground, + extrude=False, + tessellate=False, ) xml = g.to_string(verbosity=Verbosity.terse) assert "altitudeMode" not in xml assert "clampToGround" not in xml + assert "extrude" not in xml + assert "tessellate" not in xml def test_to_string_terse(self) -> None: """Test that with terse verbosity, only the necessary elements are included.""" @@ -353,11 +357,15 @@ def test_to_string_terse(self) -> None: target_id="target_id", id="my-id", altitude_mode=AltitudeMode.relative_to_ground, + extrude=True, + tessellate=True, ) xml = g.to_string(verbosity=Verbosity.terse) assert "altitudeMode>relativeToGround<" in xml + assert "extrude>11<" in xml def test_to_string_terse_unset(self) -> None: """Test that with terse verbosity, only the necessary elements are included.""" @@ -371,6 +379,8 @@ def test_to_string_terse_unset(self) -> None: assert "altitudeMode" not in xml assert "clampToGround" not in xml + assert "extrude" not in xml + assert "tessellate" not in xml def test_to_string_verbose(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -384,6 +394,8 @@ def test_to_string_verbose(self) -> None: xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>relativeToGround<" in xml + assert "extrude>0<" in xml + assert "tessellate>0<" in xml def test_to_string_verbose_default_set(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -392,11 +404,15 @@ def test_to_string_verbose_default_set(self) -> None: target_id="target_id", altitude_mode=AltitudeMode.clamp_to_ground, id="my-id", + extrude=False, + tessellate=False, ) xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>clampToGround<" in xml + assert "extrude>0<" in xml + assert "tessellate>0<" in xml def test_to_string_verbose_default(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -409,6 +425,8 @@ def test_to_string_verbose_default(self) -> None: xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>clampToGround<" in xml + assert "extrude>0<" in xml + assert "tessellate>0<" in xml def test_from_string(self) -> None: """Test the from_string method.""" From 27c391b9b0d07db4e6278e5c6a9b838f256c24ea Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Sep 2024 18:43:32 +0100 Subject: [PATCH 05/15] improve typing in geometry_tests --- tests/geometries/geometry_test.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index 98297693..da75b221 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -82,6 +82,7 @@ def test_point(self) -> None: g = Point.class_from_string(doc) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "Point", "bbox": (0.0, 1.0, 0.0, 1.0), @@ -95,6 +96,7 @@ def test_linestring(self) -> None: g = LineString.class_from_string(doc) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "LineString", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -110,6 +112,7 @@ def test_linearring(self) -> None: g = LinearRing.class_from_string(doc) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "LinearRing", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -129,6 +132,7 @@ def test_polygon(self) -> None: g = Polygon.class_from_string(doc) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "Polygon", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -154,6 +158,7 @@ def test_polygon_with_inner_boundary(self) -> None: g = Polygon.class_from_string(doc) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "Polygon", "bbox": (-1.0, -1.0, 2.0, 2.0), @@ -267,7 +272,8 @@ def test_geometrycollection_with_linearring(self) -> None: g = MultiGeometry.class_from_string(doc) - assert len(g.geometry) == 2 # type: ignore[arg-type] + assert g.geometry + assert len(g.geometry) == 2 assert g.geometry.geom_type == "GeometryCollection" @@ -518,6 +524,8 @@ def test_create_kml_geometry_point(self) -> None: """Test the create_kml_geometry function.""" g = create_kml_geometry(geo.Point(0, 1)) + assert isinstance(g, Point) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "Point", "bbox": (0.0, 1.0, 0.0, 1.0), @@ -530,6 +538,8 @@ def test_create_kml_geometry_linestring(self) -> None: """Test the create_kml_geometry function.""" g = create_kml_geometry(geo.LineString([(0, 0), (1, 1)])) + assert isinstance(g, LineString) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "LineString", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -542,6 +552,8 @@ def test_create_kml_geometry_linearring(self) -> None: """Test the create_kml_geometry function.""" g = create_kml_geometry(geo.LinearRing([(0, 0), (1, 1), (1, 0), (0, 0)])) + assert isinstance(g, LinearRing) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "LinearRing", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -557,6 +569,8 @@ def test_create_kml_geometry_polygon(self) -> None: """Test the create_kml_geometry function.""" g = create_kml_geometry(geo.Polygon([(0, 0), (1, 1), (1, 0), (0, 0)])) + assert isinstance(g, Polygon) + assert g.geometry assert g.geometry.__geo_interface__ == { "type": "Polygon", "bbox": (0.0, 0.0, 1.0, 1.0), @@ -572,6 +586,8 @@ def test_create_kml_geometry_multipoint(self) -> None: """Test the create_kml_geometry function.""" g = create_kml_geometry(geo.MultiPoint([(0, 0), (1, 1), (1, 0), (2, 2)])) + assert isinstance(g, MultiGeometry) + assert g.geometry assert len(g.geometry) == 4 assert "MultiGeometry>" in g.to_string() assert "Point>" in g.to_string() @@ -586,6 +602,8 @@ def test_create_kml_geometry_multilinestring(self) -> None: geo.MultiLineString([[(0, 0), (1, 1)], [(0, 0), (1, 1)]]), ) + assert isinstance(g, MultiGeometry) + assert g.geometry assert len(g.geometry) == 2 assert "MultiGeometry>" in g.to_string() assert "LineString>" in g.to_string() @@ -605,6 +623,9 @@ def test_create_kml_geometry_multipolygon(self) -> None: ), ), ) + + assert isinstance(g, MultiGeometry) + assert g.geometry assert len(g.geometry) == 2 assert "MultiGeometry>" in g.to_string() assert "Polygon>" in g.to_string() @@ -638,6 +659,8 @@ def test_create_kml_geometry_geometrycollection(self) -> None: g = create_kml_geometry(gc) + assert isinstance(g, MultiGeometry) + assert g.geometry assert len(g.geometry) == 7 assert "MultiGeometry>" in g.to_string() assert "LineString>" in g.to_string() From 274f14eec8415ef0e09e0321315534bcab97ae8c Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Tue, 17 Sep 2024 18:57:16 +0100 Subject: [PATCH 06/15] improve typing in point and linestring tests --- tests/geometries/linestring_test.py | 1 + tests/geometries/point_test.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/tests/geometries/linestring_test.py b/tests/geometries/linestring_test.py index bfc773f8..eb9e8b56 100644 --- a/tests/geometries/linestring_test.py +++ b/tests/geometries/linestring_test.py @@ -145,6 +145,7 @@ def test_from_string_invalid_coordinates_nan(self) -> None: "", ) + assert line_string.geometry assert len(line_string.geometry.coords) == 5 assert line_string.to_string() diff --git a/tests/geometries/point_test.py b/tests/geometries/point_test.py index d0389647..77e89d8a 100644 --- a/tests/geometries/point_test.py +++ b/tests/geometries/point_test.py @@ -123,6 +123,7 @@ def test_from_string_uppercase_altitude_mode_relaxed(self) -> None: ) assert point.geometry == geo.Point(1, 2) + assert point.altitude_mode assert point.altitude_mode.value == "relativeToGround" def test_from_string_uppercase_altitude_mode_strict(self) -> None: @@ -150,6 +151,7 @@ def test_from_string_3d(self) -> None: ) assert point.geometry == geo.Point(1, 2, 3) + assert point.altitude_mode assert point.altitude_mode.value == "absolute" assert point.extrude assert point.tessellate From 3f7368b087b866a7361117ac8f7b340f6b841c6c Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 21 Sep 2024 18:04:04 +0100 Subject: [PATCH 07/15] #216 tessellate and extrude are not on every geometry While rereading the documentation implementing the verbose and sparse verbosity parameter it became clear that these elements are not present on all geometries. This was fixed here --- fastkml/geometry.py | 143 ++++++++++++++++--------- fastkml/gx.py | 20 +--- tests/geometries/geometry_test.py | 52 ++------- tests/geometries/linestring_test.py | 71 ++++++++++++ tests/geometries/multigeometry_test.py | 8 +- tests/geometries/point_test.py | 58 +++++++--- tests/gx_test.py | 28 ++--- 7 files changed, 227 insertions(+), 153 deletions(-) diff --git a/fastkml/geometry.py b/fastkml/geometry.py index 9163f9fd..cefea0b3 100644 --- a/fastkml/geometry.py +++ b/fastkml/geometry.py @@ -320,8 +320,6 @@ class _Geometry(_BaseObject): """ - extrude: Optional[bool] - tessellate: Optional[bool] altitude_mode: Optional[AltitudeMode] def __init__( @@ -331,8 +329,6 @@ def __init__( name_spaces: Optional[Dict[str, str]] = None, id: Optional[str] = None, target_id: Optional[str] = None, - extrude: Optional[bool] = None, - tessellate: Optional[bool] = None, altitude_mode: Optional[AltitudeMode] = None, **kwargs: Any, ) -> None: @@ -359,8 +355,6 @@ def __init__( target_id=target_id, **kwargs, ) - self.extrude = extrude - self.tessellate = tessellate self.altitude_mode = altitude_mode def __repr__(self) -> str: @@ -371,38 +365,12 @@ def __repr__(self) -> str: f"name_spaces={self.name_spaces!r}, " f"id={self.id!r}, " f"target_id={self.target_id!r}, " - f"extrude={self.extrude!r}, " - f"tessellate={self.tessellate!r}, " f"altitude_mode={self.altitude_mode}, " f"**{self._get_splat()!r}," ")" ) -registry.register( - _Geometry, - item=RegistryItem( - ns_ids=("kml",), - classes=(bool,), - attr_name="extrude", - node_name="extrude", - get_kwarg=subelement_bool_kwarg, - set_element=bool_subelement, - default=False, - ), -) -registry.register( - _Geometry, - item=RegistryItem( - ns_ids=("kml",), - classes=(bool,), - attr_name="tessellate", - node_name="tessellate", - get_kwarg=subelement_bool_kwarg, - set_element=bool_subelement, - default=False, - ), -) registry.register( _Geometry, item=RegistryItem( @@ -429,6 +397,7 @@ class Point(_Geometry): https://developers.google.com/kml/documentation/kmlreference#point """ + extrude: Optional[bool] kml_coordinates: Optional[Coordinates] def __init__( @@ -439,7 +408,6 @@ def __init__( id: Optional[str] = None, target_id: Optional[str] = None, extrude: Optional[bool] = None, - tessellate: Optional[bool] = None, altitude_mode: Optional[AltitudeMode] = None, geometry: Optional[geo.Point] = None, kml_coordinates: Optional[Coordinates] = None, @@ -476,13 +444,13 @@ def __init__( else None ) self.kml_coordinates = kml_coordinates + self.extrude = extrude + kwargs.pop("tessellate", None) super().__init__( ns=ns, id=id, name_spaces=name_spaces, target_id=target_id, - extrude=extrude, - tessellate=tessellate, altitude_mode=altitude_mode, **kwargs, ) @@ -503,7 +471,6 @@ def __repr__(self) -> str: f"id={self.id!r}, " f"target_id={self.target_id!r}, " f"extrude={self.extrude!r}, " - f"tessellate={self.tessellate!r}, " f"altitude_mode={self.altitude_mode}, " f"kml_coordinates={self.kml_coordinates!r}, " f"**{self._get_splat()!r}," @@ -551,6 +518,18 @@ def geometry(self) -> Optional[geo.Point]: set_element=xml_subelement, ), ) +registry.register( + Point, + item=RegistryItem( + ns_ids=("kml",), + classes=(bool,), + attr_name="extrude", + node_name="extrude", + get_kwarg=subelement_bool_kwarg, + set_element=bool_subelement, + default=False, + ), +) class LineString(_Geometry): @@ -567,6 +546,10 @@ class LineString(_Geometry): https://developers.google.com/kml/documentation/kmlreference#linestring """ + extrude: Optional[bool] + tessellate: Optional[bool] + kml_coordinates: Optional[Coordinates] + def __init__( self, *, @@ -607,13 +590,13 @@ def __init__( if kml_coordinates is None: kml_coordinates = Coordinates(coords=geometry.coords) if geometry else None self.kml_coordinates = kml_coordinates + self.extrude = extrude + self.tessellate = tessellate super().__init__( ns=ns, name_spaces=name_spaces, id=id, target_id=target_id, - extrude=extrude, - tessellate=tessellate, altitude_mode=altitude_mode, **kwargs, ) @@ -676,6 +659,31 @@ def geometry(self) -> Optional[geo.LineString]: ), ) +registry.register( + LineString, + item=RegistryItem( + ns_ids=("kml",), + classes=(bool,), + attr_name="extrude", + node_name="extrude", + get_kwarg=subelement_bool_kwarg, + set_element=bool_subelement, + default=False, + ), +) +registry.register( + LineString, + item=RegistryItem( + ns_ids=("kml",), + classes=(bool,), + attr_name="tessellate", + node_name="tessellate", + get_kwarg=subelement_bool_kwarg, + set_element=bool_subelement, + default=False, + ), +) + class LinearRing(LineString): """ @@ -1025,6 +1033,8 @@ class Polygon(_Geometry): https://developers.google.com/kml/documentation/kmlreference#polygon """ + extrude: Optional[bool] + tessellate: Optional[bool] outer_boundary_is: Optional[OuterBoundaryIs] inner_boundary_is: Optional[InnerBoundaryIs] @@ -1088,13 +1098,13 @@ def __init__( inner_boundary_is = InnerBoundaryIs(geometries=geometry.interiors) self.outer_boundary_is = outer_boundary_is self.inner_boundary_is = inner_boundary_is + self.extrude = extrude + self.tessellate = tessellate super().__init__( ns=ns, name_spaces=name_spaces, id=id, target_id=target_id, - extrude=extrude, - tessellate=tessellate, altitude_mode=altitude_mode, **kwargs, ) @@ -1181,6 +1191,30 @@ def __repr__(self) -> str: set_element=xml_subelement, ), ) +registry.register( + Polygon, + item=RegistryItem( + ns_ids=("kml",), + classes=(bool,), + attr_name="extrude", + node_name="extrude", + get_kwarg=subelement_bool_kwarg, + set_element=bool_subelement, + default=False, + ), +) +registry.register( + Polygon, + item=RegistryItem( + ns_ids=("kml",), + classes=(bool,), + attr_name="tessellate", + node_name="tessellate", + get_kwarg=subelement_bool_kwarg, + set_element=bool_subelement, + default=False, + ), +) def create_multigeometry( @@ -1279,7 +1313,7 @@ def create_kml_geometry( raise KMLWriteError(msg) # pragma: no cover -class MultiGeometry(_Geometry): +class MultiGeometry(_BaseObject): """A container for zero or more geometry primitives.""" kml_geometries: List[Union[Point, LineString, Polygon, LinearRing, Self]] @@ -1313,17 +1347,26 @@ def __init__( The ID of the KML element. target_id : str, optional The target ID of the KML element. - extrude : bool, optional - Specifies whether to extend the geometry to the ground. - tessellate : bool, optional - Specifies whether to allow the geometry to follow the terrain. - altitude_mode : AltitudeMode, optional - The altitude mode of the geometry. kml_geometries : iterable of Point, LineString, Polygon, LinearRing, MultiGeometry A collection of KML geometries. geometry : MultiGeometryType, optional A multi-geometry object. + Parameters for geometry and kml_geometries are mutually exclusive. + When geometry is provided, kml_geometries will be created from it and + you can specify additional parameters like extrude, tessellate, and + altitude_mode which will be set on the individual geometries. + extrude : bool, optional + Specifies whether to extend the geometry to the ground. + This is not set on the multi-geometry itself, but on the individual + geometries. + tessellate : bool, optional + Specifies whether to allow the geometry to follow the terrain. + This is not set on the multi-geometry itself, but on the individual + geometries. + altitude_mode : AltitudeMode, optional + The altitude mode of the geometry. This is not set on the multi-geometry + itself, but on the individual geometries. **kwargs : any Additional keyword arguments. @@ -1358,9 +1401,6 @@ def __init__( name_spaces=name_spaces, id=id, target_id=target_id, - extrude=extrude, - tessellate=tessellate, - altitude_mode=altitude_mode, **kwargs, ) @@ -1376,9 +1416,6 @@ def __repr__(self) -> str: f"name_spaces={self.name_spaces!r}, " f"id={self.id!r}, " f"target_id={self.target_id!r}, " - f"extrude={self.extrude!r}, " - f"tessellate={self.tessellate!r}, " - f"altitude_mode={self.altitude_mode}, " f"kml_geometries={self.kml_geometries!r}, " f"**{self._get_splat()!r}," ")" diff --git a/fastkml/gx.py b/fastkml/gx.py index 071e1321..0011fad8 100644 --- a/fastkml/gx.py +++ b/fastkml/gx.py @@ -250,8 +250,6 @@ def __init__( name_spaces: Optional[Dict[str, str]] = None, id: Optional[str] = None, target_id: Optional[str] = None, - extrude: Optional[bool] = None, - tessellate: Optional[bool] = None, altitude_mode: Optional[AltitudeMode] = None, geometry: Optional[geo.LineString] = None, track_items: Optional[Iterable[TrackItem]] = None, @@ -270,10 +268,6 @@ def __init__( The ID of the GX object, by default None target_id : Optional[str], optional The target ID of the GX object, by default None - extrude : Optional[bool], optional - Whether to extrude the GX object, by default None - tessellate : Optional[bool], optional - Whether to tessellate the GX object, by default None altitude_mode : Optional[AltitudeMode], optional The altitude mode of the GX object, by default None geometry : Optional[geo.LineString], optional @@ -300,8 +294,6 @@ def __init__( name_spaces=name_spaces, id=id, target_id=target_id, - extrude=extrude, - tessellate=tessellate, altitude_mode=altitude_mode, **kwargs, ) @@ -322,8 +314,6 @@ def __repr__(self) -> str: f"name_spaces={self.name_spaces!r}, " f"id={self.id!r}, " f"target_id={self.target_id!r}, " - f"extrude={self.extrude!r}, " - f"tessellate={self.tessellate!r}, " f"altitude_mode={self.altitude_mode}, " f"geometry={self.geometry!r}, " f"track_items={self.track_items!r}, " @@ -613,8 +603,6 @@ def __init__( name_spaces: Optional[Dict[str, str]] = None, id: Optional[str] = None, target_id: Optional[str] = None, - extrude: Optional[bool] = None, - tessellate: Optional[bool] = None, altitude_mode: Optional[AltitudeMode] = None, geometry: Optional[geo.MultiLineString] = None, tracks: Optional[Iterable[Track]] = None, @@ -631,8 +619,6 @@ def __init__( and URIs. id (Optional[str]): The ID of the GX object. target_id (Optional[str]): The target ID of the GX object. - extrude (Optional[bool]): The extrude flag of the GX object. - tessellate (Optional[bool]): The tessellate flag of the GX object. altitude_mode (Optional[AltitudeMode]): The altitude mode of the GX object. geometry (Optional[geo.MultiLineString]): The geometry of the GX object. tracks (Optional[Iterable[Track]]): The tracks of the GX object. @@ -645,7 +631,7 @@ def __init__( """ if geometry and tracks: - msg = "Cannot specify both geometry and track_items" + msg = "Cannot specify both geometry and tracks" raise ValueError(msg) if geometry: tracks = multilinestring_to_tracks(geometry, ns=ns) @@ -656,8 +642,6 @@ def __init__( name_spaces=name_spaces, id=id, target_id=target_id, - extrude=extrude, - tessellate=tessellate, altitude_mode=altitude_mode, **kwargs, ) @@ -670,8 +654,6 @@ def __repr__(self) -> str: f"name_spaces={self.name_spaces!r}, " f"id={self.id!r}, " f"target_id={self.target_id!r}, " - f"extrude={self.extrude!r}, " - f"tessellate={self.tessellate!r}, " f"altitude_mode={self.altitude_mode}, " f"geometry={self.geometry!r}, " f"tracks={self.tracks!r}, " diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index da75b221..0aff3c53 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -73,7 +73,7 @@ def test_tesselate(self) -> None: g = Point.class_from_string(doc) - assert g.tessellate is True + assert not hasattr(g, "tessellate") def test_point(self) -> None: doc = """ @@ -287,9 +287,7 @@ def test_init(self) -> None: assert g.ns == "{http://www.opengis.net/kml/2.2}" assert g.target_id == "" assert g.id == "" - assert g.extrude is None assert g.altitude_mode is None - assert g.tessellate is None def test_init_with_args(self) -> None: """Test the init method with arguments.""" @@ -297,17 +295,13 @@ def test_init_with_args(self) -> None: ns="", target_id="target_id", id="id", - extrude=True, altitude_mode=AltitudeMode.clamp_to_ground, - tessellate=True, ) assert g.ns == "" assert g.target_id == "target_id" assert g.id == "id" - assert g.extrude is True assert g.altitude_mode == AltitudeMode.clamp_to_ground - assert g.tessellate is True def test_to_string(self) -> None: """Test the to_string method.""" @@ -334,9 +328,9 @@ def test_to_string_with_args(self) -> None: assert "http://www.opengis.net/kml/2.3" in g.to_string() assert 'targetId="target_id"' in g.to_string() assert 'id="my-id"' in g.to_string() - assert "extrude>1relativeToGround<" in g.to_string() - assert "tessellate>1<" in g.to_string() + assert "tessellate" not in g.to_string() def test_to_string_terse_default(self) -> None: """Test that with terse verbosity, only the necessary elements are included.""" @@ -345,16 +339,12 @@ def test_to_string_terse_default(self) -> None: target_id="target_id", id="my-id", altitude_mode=AltitudeMode.clamp_to_ground, - extrude=False, - tessellate=False, ) xml = g.to_string(verbosity=Verbosity.terse) assert "altitudeMode" not in xml assert "clampToGround" not in xml - assert "extrude" not in xml - assert "tessellate" not in xml def test_to_string_terse(self) -> None: """Test that with terse verbosity, only the necessary elements are included.""" @@ -363,15 +353,11 @@ def test_to_string_terse(self) -> None: target_id="target_id", id="my-id", altitude_mode=AltitudeMode.relative_to_ground, - extrude=True, - tessellate=True, ) xml = g.to_string(verbosity=Verbosity.terse) assert "altitudeMode>relativeToGround<" in xml - assert "extrude>11<" in xml def test_to_string_terse_unset(self) -> None: """Test that with terse verbosity, only the necessary elements are included.""" @@ -385,8 +371,6 @@ def test_to_string_terse_unset(self) -> None: assert "altitudeMode" not in xml assert "clampToGround" not in xml - assert "extrude" not in xml - assert "tessellate" not in xml def test_to_string_verbose(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -400,8 +384,6 @@ def test_to_string_verbose(self) -> None: xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>relativeToGround<" in xml - assert "extrude>0<" in xml - assert "tessellate>0<" in xml def test_to_string_verbose_default_set(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -410,15 +392,11 @@ def test_to_string_verbose_default_set(self) -> None: target_id="target_id", altitude_mode=AltitudeMode.clamp_to_ground, id="my-id", - extrude=False, - tessellate=False, ) xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>clampToGround<" in xml - assert "extrude>0<" in xml - assert "tessellate>0<" in xml def test_to_string_verbose_default(self) -> None: """Test that with verbose verbosity, all elements are included.""" @@ -431,8 +409,6 @@ def test_to_string_verbose_default(self) -> None: xml = g.to_string(verbosity=Verbosity.verbose) assert "altitudeMode>clampToGround<" in xml - assert "extrude>0<" in xml - assert "tessellate>0<" in xml def test_from_string(self) -> None: """Test the from_string method.""" @@ -448,9 +424,9 @@ def test_from_string(self) -> None: assert g.ns == "{http://www.opengis.net/kml/2.2}" assert g.target_id == "target_id" assert g.id == "my-id" - assert g.extrude is True assert g.altitude_mode == AltitudeMode.relative_to_ground - assert g.tessellate is True + assert not hasattr(g, "tessellate") + assert not hasattr(g, "extrude") def test_from_string_invalid_altitude_mode_strict(self) -> None: """Test the from_string method.""" @@ -476,18 +452,6 @@ def test_from_string_invalid_altitude_mode_relaxed(self) -> None: assert geom.altitude_mode is None - def test_from_string_invalid_extrude(self) -> None: - """Test the from_string method.""" - with pytest.raises( - exceptions.KMLParseError, - ): - _Geometry.class_from_string( - '<_Geometry id="my-id" targetId="target_id" ' - 'xmlns="http://www.opengis.net/kml/2.2">' - "invalid" - "", - ) - def test_from_minimal_string(self) -> None: g = _Geometry.class_from_string( '<_Geometry xmlns="http://www.opengis.net/kml/2.2/" />', @@ -496,9 +460,7 @@ def test_from_minimal_string(self) -> None: assert g.ns == "{http://www.opengis.net/kml/2.2}" assert g.target_id == "" assert g.id == "" - assert g.extrude is None assert g.altitude_mode is None - assert g.tessellate is None def test_from_string_omitting_ns(self) -> None: """Test the from_string method.""" @@ -514,9 +476,9 @@ def test_from_string_omitting_ns(self) -> None: assert g.ns == "{http://www.opengis.net/kml/2.2}" assert g.target_id == "target_id" assert g.id == "my-id" - assert g.extrude is True assert g.altitude_mode == AltitudeMode.relative_to_ground - assert g.tessellate is True + assert not hasattr(g, "tessellate") + assert not hasattr(g, "extrude") class TestCreateKmlGeometry(StdLibrary): diff --git a/tests/geometries/linestring_test.py b/tests/geometries/linestring_test.py index eb9e8b56..db87839d 100644 --- a/tests/geometries/linestring_test.py +++ b/tests/geometries/linestring_test.py @@ -19,6 +19,8 @@ import pygeoif.geometry as geo import pytest +from fastkml import exceptions +from fastkml.enums import Verbosity from fastkml.exceptions import KMLParseError from fastkml.geometry import LineString from tests.base import Lxml @@ -149,6 +151,75 @@ def test_from_string_invalid_coordinates_nan(self) -> None: assert len(line_string.geometry.coords) == 5 assert line_string.to_string() + def test_from_string_invalid_extrude(self) -> None: + """Test the from_string method.""" + with pytest.raises( + exceptions.KMLParseError, + ): + LineString.class_from_string( + '' + "invalid" + "", + ) + + def test_from_string_invalid_tesselate(self) -> None: + """Test the from_string method.""" + with pytest.raises( + exceptions.KMLParseError, + ): + LineString.class_from_string( + '' + "invalid" + "", + ) + + def test_to_string_terse_default(self) -> None: + ls = geo.LineString(((1, 2), (2, 0))) + line_string = LineString(geometry=ls, extrude=False, tessellate=False) + + xml = line_string.to_string(verbosity=Verbosity.terse) + + assert "tessellate" not in xml + assert "extrude" not in xml + + def test_to_string_terse(self) -> None: + ls = geo.LineString(((1, 2), (2, 0))) + line_string = LineString(geometry=ls, extrude=True, tessellate=True) + + xml = line_string.to_string(verbosity=Verbosity.terse) + + assert "tessellate>11 None: + ls = geo.LineString(((1, 2), (2, 0))) + line_string = LineString(geometry=ls, extrude=False, tessellate=False) + + xml = line_string.to_string(verbosity=Verbosity.verbose) + + assert "tessellate>00 None: + ls = geo.LineString(((1, 2), (2, 0))) + line_string = LineString(geometry=ls, extrude=True, tessellate=True) + + xml = line_string.to_string(verbosity=Verbosity.verbose) + + assert "tessellate>11 None: + ls = geo.LineString(((1, 2), (2, 0))) + line_string = LineString(geometry=ls) + + xml = line_string.to_string(verbosity=Verbosity.verbose) + + assert "tessellate>00 None: mg = MultiGeometry.class_from_string(xml) assert mg.geometry is None - assert "MultiGeometry>" in mg.to_string() - assert "coordinates>" not in mg.to_string() - assert mg.extrude is False - assert mg.tessellate is False + assert "MultiGeometry" in mg.to_string() + assert "coordinates" not in mg.to_string() + assert not hasattr(mg, "extrude") + assert not hasattr(mg, "tessellate") class TestMultiPointLxml(Lxml, TestMultiPointStdLibrary): diff --git a/tests/geometries/point_test.py b/tests/geometries/point_test.py index 77e89d8a..178a3c6b 100644 --- a/tests/geometries/point_test.py +++ b/tests/geometries/point_test.py @@ -19,6 +19,7 @@ import pygeoif.geometry as geo import pytest +from fastkml.enums import Verbosity from fastkml.exceptions import KMLParseError from fastkml.geometry import Point from tests.base import Lxml @@ -56,6 +57,51 @@ def test_to_string_3d(self) -> None: assert "Point" in point.to_string() assert "coordinates>1.000000,2.000000,3.000000 None: + """Test the to_string method, exclude default for extrude in terse mode.""" + p = geo.Point(1, 2) + + point = Point(geometry=p, extrude=False) + + assert "coordinates>" in point.to_string(verbosity=Verbosity.terse) + assert "extrude" not in point.to_string(verbosity=Verbosity.terse) + + def test_to_string_terse_non_default(self) -> None: + """Test the to_string method, include extrude when true in terse mode.""" + p = geo.Point(1, 2) + + point = Point(geometry=p, extrude=True) + + assert "coordinates>" in point.to_string(verbosity=Verbosity.terse) + assert "extrude>1 None: + """Test the to_string method, include default for extrude in verbose mode.""" + p = geo.Point(1, 2) + + point = Point(geometry=p, extrude=False) + + assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose) + assert "extrude>0 None: + """Test the to_string method, include extrude when true in verbose mode.""" + p = geo.Point(1, 2) + + point = Point(geometry=p, extrude=True) + + assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose) + assert "extrude>1 None: + """Test the to_string method, include extrude when true in verbose mode.""" + p = geo.Point(1, 2) + + point = Point(geometry=p, extrude=False) + + assert "coordinates>" in point.to_string(verbosity=Verbosity.verbose) + assert "extrude>0 None: """Test the to_string method.""" p = geo.Point(1, 2) @@ -110,7 +156,6 @@ def test_from_string_2d(self) -> None: assert point.geometry == geo.Point(1, 2) assert point.altitude_mode is None assert point.extrude is None - assert point.tessellate is None def test_from_string_uppercase_altitude_mode_relaxed(self) -> None: """Test the from_string method for an uppercase altitude mode.""" @@ -154,7 +199,6 @@ def test_from_string_3d(self) -> None: assert point.altitude_mode assert point.altitude_mode.value == "absolute" assert point.extrude - assert point.tessellate def test_empty_from_string(self) -> None: """Test the from_string method.""" @@ -210,16 +254,6 @@ def test_from_string_invalid_coordinates_non_numerical(self) -> None: "a,b,c", ) - def test_from_string_invalid_coordinates_nan(self) -> None: - with pytest.raises( - KMLParseError, - match=r"^Invalid coordinates in", - ): - Point.class_from_string( - '' - "a,b", - ) - class TestPointLxml(Lxml, TestPoint): """Test with lxml.""" diff --git a/tests/gx_test.py b/tests/gx_test.py index 90d67580..9316830e 100644 --- a/tests/gx_test.py +++ b/tests/gx_test.py @@ -82,16 +82,12 @@ def test_multitrack(self) -> None: ns="", id="", target_id="", - extrude=None, - tessellate=None, altitude_mode=None, tracks=[ Track( ns="{http://www.google.com/kml/ext/2.2}", id="", target_id="", - extrude=None, - tessellate=None, altitude_mode=None, track_items=[ TrackItem( @@ -124,8 +120,6 @@ def test_multitrack(self) -> None: ns="{http://www.google.com/kml/ext/2.2}", id="", target_id="", - extrude=None, - tessellate=None, altitude_mode=None, track_items=[ TrackItem( @@ -171,16 +165,12 @@ def test_track_from_linestring(self) -> None: id="track1", target_id="track2", altitude_mode=AltitudeMode.absolute, - extrude=True, - tessellate=True, geometry=ls, ) assert "1" in track.to_string() - assert "tessellate>1" in track.to_string() assert "altitudeMode>absolute" in track.to_string() assert "coord>" in track.to_string() assert "angles" in track.to_string() @@ -211,7 +201,10 @@ def test_track_from_track_items_and_geometry(self) -> None: angle = Angle() track_items = [TrackItem(when=time1, coord=geo.Point(1, 2), angle=angle)] - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^Cannot specify both geometry and track_items$", + ): Track( track_items=track_items, geometry=ls, @@ -268,8 +261,6 @@ def test_track_from_str(self) -> None: ns="", id="", target_id="", - extrude=None, - tessellate=None, altitude_mode=None, track_items=[ TrackItem( @@ -345,16 +336,12 @@ def test_from_multilinestring(self) -> None: ns="", id=None, target_id=None, - extrude=None, - tessellate=None, altitude_mode=None, tracks=[ Track( ns="", id=None, target_id=None, - extrude=None, - tessellate=None, altitude_mode=None, track_items=[ TrackItem(when=None, coord=geo.Point(0, 0), angle=None), @@ -367,8 +354,6 @@ def test_from_multilinestring(self) -> None: ns="", id=None, target_id=None, - extrude=None, - tessellate=None, altitude_mode=None, track_items=[ TrackItem(when=None, coord=geo.Point(0.0, 0.0), angle=None), @@ -463,7 +448,10 @@ def test_from_multilinestring_and_tracks(self) -> None: ), ] - with pytest.raises(ValueError): + with pytest.raises( + ValueError, + match="^Cannot specify both geometry and tracks$", + ): MultiTrack(geometry=lines, tracks=[Track(track_items=track_items)]) From f45de18723f85f48d4514ddc1f21c255f28b82e0 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 21 Sep 2024 18:31:57 +0100 Subject: [PATCH 08/15] #216 Refactor geometry tests for verbosity and default values --- tests/geometries/multigeometry_test.py | 28 +++++++++++ tests/geometries/polygon_test.py | 65 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/tests/geometries/multigeometry_test.py b/tests/geometries/multigeometry_test.py index fb67c271..7d9b7783 100644 --- a/tests/geometries/multigeometry_test.py +++ b/tests/geometries/multigeometry_test.py @@ -17,6 +17,7 @@ """Test the geometry classes.""" import pygeoif.geometry as geo +from fastkml.enums import Verbosity from fastkml.geometry import MultiGeometry from tests.base import Lxml from tests.base import StdLibrary @@ -261,6 +262,33 @@ def test_multi_geometries(self) -> None: assert "Polygon>" in mg.to_string() assert "MultiGeometry>" in mg.to_string() + def test_multi_geometries_verbose(self) -> None: + p = geo.Point(1, 2) + ls = geo.LineString(((1, 2), (2, 0))) + lr = geo.LinearRing(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0))) + poly = geo.Polygon( + [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], + [[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]], + ) + gc = geo.GeometryCollection([p, ls, lr, poly]) + mp = geo.MultiPolygon( + [ + ( + ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)), + [((0.1, 0.1), (0.1, 0.2), (0.2, 0.2), (0.2, 0.1))], + ), + (((0.0, 0.0), (0.0, 2.0), (1.0, 1.0), (1.0, 0.0)),), + ], + ) + ml = geo.MultiLineString([[(1, 2), (3, 4)], [(5, 6), (7, 8)]]) + mgc = geo.GeometryCollection([gc, mp, ml]) + mg = MultiGeometry(ns="", geometry=mgc) + + xml = mg.to_string(verbosity=Verbosity.verbose) + assert xml.count("tessellate>0<") == 12 # points do not have tessellate + assert xml.count("extrude>0<") == 13 + assert xml.count("altitudeMode>clampToGround<") == 13 + def test_multi_geometries_read(self) -> None: xml = ( '' diff --git a/tests/geometries/polygon_test.py b/tests/geometries/polygon_test.py index cecb1cdc..9421b8fe 100644 --- a/tests/geometries/polygon_test.py +++ b/tests/geometries/polygon_test.py @@ -18,6 +18,8 @@ import pygeoif.geometry as geo +from fastkml.enums import AltitudeMode +from fastkml.enums import Verbosity from fastkml.geometry import Polygon from tests.base import Lxml from tests.base import StdLibrary @@ -61,6 +63,69 @@ def test_exterior_interior(self) -> None: "0.900000,0.100000 0.100000,0.100000" ) in polygon.to_string() + def test_exterior_interior_tessellate_extrude_altitude_mode(self) -> None: + """ + Test exterior and interior with tessellate, extrude and altitude mode. + + This should be set on the Polygon level, not on the LinearRing level. + """ + poly = geo.Polygon( + [(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)], + [[(0.1, 0.1), (0.1, 0.9), (0.9, 0.9), (0.9, 0.1), (0.1, 0.1)]], + ) + polygon = Polygon( + ns="", + geometry=poly, + extrude=True, + tessellate=True, + altitude_mode=AltitudeMode.relative_to_ground, + ) + + xml = polygon.to_string() + assert xml.count("extrude>11relativeToGround None: + """Test the to_string method, exclude default for extrude in terse mode.""" + poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) + + polygon = Polygon(ns="", geometry=poly, extrude=False) + + assert "extrude>0 None: + """Test the to_string method, include extrude when true in terse mode.""" + poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) + + polygon = Polygon(ns="", geometry=poly, extrude=True) + + assert "extrude>1 None: + """Test the to_string method, include default for extrude in verbose mode.""" + poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) + + polygon = Polygon(ns="", geometry=poly, extrude=False) + + assert "extrude>0 None: + """Test the to_string method, include extrude when true in verbose mode.""" + poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) + + polygon = Polygon(ns="", geometry=poly, extrude=True) + + assert "extrude>1 None: + """Test the to_string method, include extrude when true in verbose mode.""" + poly = geo.Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) + + polygon = Polygon(ns="", geometry=poly) + + assert "extrude>0 None: """Test exterior only.""" doc = """ From 46f25b4de628a528bcbef73d87640ea78c5c428e Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sun, 22 Sep 2024 01:44:26 +0100 Subject: [PATCH 09/15] update readme --- README.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 700145ea..c099235e 100644 --- a/README.rst +++ b/README.rst @@ -115,11 +115,7 @@ Optional pip install --pre "fastkml[lxml]" Limitations -=========== - -*Tesselate*, *Extrude* and *Altitude Mode* are assigned to a Geometry or -Geometry collection (MultiGeometry). You cannot assign different values of -*Tesselate*, *Extrude* or *Altitude Mode* on parts of a MultiGeometry. +============ Currently, the only major feature missing for the full Google Earth experience is the `gx extension From 638d51c4042fd55d218ed30a74350fccc44c29fe Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Mon, 23 Sep 2024 22:35:27 +0100 Subject: [PATCH 10/15] Refactor contributing documentation and add testing instructions --- docs/contributing.rst | 81 ++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 319aaa39..d575f882 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -11,58 +11,49 @@ especially in the following ways: * Commenting on open issues and pull requests * Suggesting new features +Setting Up Your Environment +--------------------------- -Pull Requests -------------- +First, clone the repository: -Start by submitting a pull request on GitHub against the ``develop`` branch of the -repository. Your pull request should provide a good description of the change -you are making, and/or the bug that you are fixing. +.. code-block:: bash + git clone https://github.com/yourusername/fastkml.git + cd fastkml -Running Tests Locally ---------------------- +Next, set up a virtual environment. This helps to manage dependencies and avoid conflicts: -You can make use of tox_ >= 1.8 to test the entire matrix of options: +.. code-block:: bash -* with / without lxml -* py36,py37,py38,py39 + python3 -m venv venv + source venv/bin/activate # On Windows use `venv\Scripts\activate` -as well as pep8 style checking in a single call (this approximates what happens -when the package is run through Travis-CI) +Then, install the required packages: -.. code-block:: python +.. code-block:: bash - # Install tox - pip install tox>=1.8 + pip install -e ".[dev]" - # Run tox - tox +Install the ``pre-commit`` hook with: - # Or optionally - # (to skip tests for Python versions you do not have installed) - tox --skip-missing-interpreters +.. code-block:: bash -This will run through all of the tests and produce an output similar to:: + pre-commit install + +and check the code with: + +.. code-block:: bash + + pre-commit run --all-files - ______________________________________________________ summary ______________________________________________________ - SKIPPED: py36: InterpreterNotFound: python3.6 - py37: commands succeeded - py38: commands succeeded - py39: commands succeeded - SKIPPED: py36-lxml: InterpreterNotFound: python3.6 - py37-lxml: commands succeeded - py38-lxml: commands succeeded - py39-lxml: commands succeeded - pep8: commands succeeded - docs: commands succeeded - congratulations :) +Running the Tests +----------------- -You are primarily looking for the ``congratulations :)`` line at the bottom, -signifying that the code is working as expected on all configurations -available. +To run the tests, simply use: -.. _tox: https://pypi.python.org/pypi/tox +.. code-block:: bash + + pytest coverage ~~~~~~~~ @@ -70,17 +61,13 @@ coverage You can also run the tests with coverage_ to see which lines are covered by the tests. This is useful for writing new tests to cover any uncovered lines:: - pytest tests --cov=fastkml --cov=tests --cov-report=xml - +.. code-block:: bash -pre-commit -~~~~~~~~~~~ + pytest --cov=fastkml --cov-report=term -Install the ``pre-commit`` hook with:: - pip install pre-commit - pre-commit install +Tips +---- -and check the code with:: - - pre-commit run --all-files +- Commit often, commit early. +- Make a draft PR while you are still working on it to give your work some visibility. From c59d4d31d72f1c63e0e0a5b9d46dc4a40c7a4f75 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 5 Oct 2024 11:13:20 +0100 Subject: [PATCH 11/15] z precissiso in kml file --- tests/ogc_conformance/data/kml/Document-places.kml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ogc_conformance/data/kml/Document-places.kml b/tests/ogc_conformance/data/kml/Document-places.kml index b35dd2ed..97129097 100644 --- a/tests/ogc_conformance/data/kml/Document-places.kml +++ b/tests/ogc_conformance/data/kml/Document-places.kml @@ -4,13 +4,13 @@ place123 - -95.44,40.42,0 + -95.44,40.42,0.00 place456 - -95.43,40.42,0 + -95.43,40.42,0.00 From 1f75afa1de344ca19f9d6820a92d97a75fbe1733 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 5 Oct 2024 11:41:35 +0100 Subject: [PATCH 12/15] #216 add default values for features --- fastkml/features.py | 6 +++++- tests/geometries/geometry_test.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fastkml/features.py b/fastkml/features.py index 4f809f0f..3261316f 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -353,6 +353,7 @@ def __repr__(self) -> str: classes=(bool,), get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=True, ), ) registry.register( @@ -364,6 +365,7 @@ def __repr__(self) -> str: classes=(bool,), get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=False, ), ) registry.register( @@ -745,7 +747,7 @@ class NetworkLink(_Feature): For example, Google Earth would fly to the view of the parent Document, not the of the Placemarks contained within the Document. (required) - https://developers.google.com/kml/documentation/kmlreference#link + https://developers.google.com/kml/documentation/kmlreference#networklink """ refresh_visibility: Optional[bool] @@ -921,6 +923,7 @@ def __bool__(self) -> bool: classes=(bool,), get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=False, ), ) registry.register( @@ -932,6 +935,7 @@ def __bool__(self) -> bool: classes=(bool,), get_kwarg=subelement_bool_kwarg, set_element=bool_subelement, + default=False, ), ) registry.register( diff --git a/tests/geometries/geometry_test.py b/tests/geometries/geometry_test.py index 83c59cdd..472e3bf5 100644 --- a/tests/geometries/geometry_test.py +++ b/tests/geometries/geometry_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 - 2023 Christian Ledermann +# Copyright (C) 2021 - 2024 Christian Ledermann # # This library is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free From be8998fa1270b2d7afd58b99d88644567093bde0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 5 Oct 2024 10:43:56 +0000 Subject: [PATCH 13/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/geometries/linestring_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/geometries/linestring_test.py b/tests/geometries/linestring_test.py index db87839d..037b572f 100644 --- a/tests/geometries/linestring_test.py +++ b/tests/geometries/linestring_test.py @@ -163,7 +163,7 @@ def test_from_string_invalid_extrude(self) -> None: "", ) - def test_from_string_invalid_tesselate(self) -> None: + def test_from_string_invalid_tessellate(self) -> None: """Test the from_string method.""" with pytest.raises( exceptions.KMLParseError, From b1165ffd04048347be2ec6aa7f9368ce6a0dae4c Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 5 Oct 2024 11:48:43 +0100 Subject: [PATCH 14/15] fix registry test typing --- tests/registry_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/registry_test.py b/tests/registry_test.py index 26ac2cb0..9dbf68b0 100644 --- a/tests/registry_test.py +++ b/tests/registry_test.py @@ -66,6 +66,7 @@ def set_element( node_name: str, precision: Optional[int], verbosity: Optional[Verbosity], + default: Any, ) -> None: """Get an attribute from an XML object.""" From 69a4c7bb2b5748a10cee830bfce93d49fbe23643 Mon Sep 17 00:00:00 2001 From: Christian Ledermann Date: Sat, 5 Oct 2024 12:22:41 +0100 Subject: [PATCH 15/15] Verbosity is always passed --- fastkml/features.py | 1 + fastkml/helpers.py | 26 +++++++++++++------------- fastkml/registry.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/fastkml/features.py b/fastkml/features.py index 3261316f..73cb809d 100644 --- a/fastkml/features.py +++ b/fastkml/features.py @@ -747,6 +747,7 @@ class NetworkLink(_Feature): For example, Google Earth would fly to the view of the parent Document, not the of the Placemarks contained within the Document. (required) + https://developers.google.com/kml/documentation/kmlreference#networklink """ diff --git a/fastkml/helpers.py b/fastkml/helpers.py index ee35ad19..e533b56f 100644 --- a/fastkml/helpers.py +++ b/fastkml/helpers.py @@ -84,7 +84,7 @@ def get_value( obj: _XMLObject, *, attr_name: str, - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[Any], ) -> Optional[Any]: """ @@ -116,7 +116,7 @@ def node_text( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[str], ) -> None: """ @@ -161,7 +161,7 @@ def text_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[str], ) -> None: """ @@ -202,7 +202,7 @@ def text_attribute( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[str], ) -> None: """ @@ -239,7 +239,7 @@ def bool_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[bool], ) -> None: """ @@ -276,7 +276,7 @@ def int_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[int], ) -> None: """ @@ -313,7 +313,7 @@ def int_attribute( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[int], ) -> None: """ @@ -346,7 +346,7 @@ def float_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[float], ) -> None: """Set the value of an attribute from a subelement with a text node.""" @@ -366,7 +366,7 @@ def float_attribute( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[float], ) -> None: """Set the value of an attribute.""" @@ -382,7 +382,7 @@ def enum_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[Enum], ) -> None: """Set the value of an attribute from a subelement with a text node.""" @@ -402,7 +402,7 @@ def enum_attribute( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[Enum], ) -> None: """Set the value of an attribute.""" @@ -418,7 +418,7 @@ def xml_subelement( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[_XMLObject], ) -> None: """ @@ -455,7 +455,7 @@ def xml_subelement_list( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Optional[List[_XMLObject]], ) -> None: """ diff --git a/fastkml/registry.py b/fastkml/registry.py index 94aaea7a..1598c74e 100644 --- a/fastkml/registry.py +++ b/fastkml/registry.py @@ -67,7 +67,7 @@ def __call__( attr_name: str, node_name: str, precision: Optional[int], - verbosity: Optional[Verbosity], + verbosity: Verbosity, default: Any, ) -> None: ...