Skip to content

Commit

Permalink
Merge branch 'develop' into test_coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder authored Oct 13, 2024
2 parents c106b2c + 04ec3d7 commit 349436a
Show file tree
Hide file tree
Showing 18 changed files with 1,300 additions and 82 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
pip install -e ".[tests, lxml]"
- name: Test with pytest
run: |
pytest tests --cov=fastkml --cov=tests --cov-fail-under=88 --cov-report=xml
pytest tests --cov=fastkml --cov=tests --cov-fail-under=95 --cov-report=xml
- name: "Upload coverage to Codecov"
if: ${{ matrix.python-version==3.11 }}
uses: codecov/codecov-action@v4
Expand Down Expand Up @@ -127,6 +127,9 @@ jobs:
needs: [cpython, static-tests, pypy, cpython-lxml, doctest-lxml]
name: Build and publish to PyPI and TestPyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Set up Python
Expand All @@ -150,11 +153,8 @@ jobs:
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI for push to main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
...
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ repos:
rev: v3.17.0
hooks:
- id: pyupgrade
args: ["--py3-plus", "--py37-plus"]
args: ["--py3-plus", "--py38-plus"]
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
Expand Down Expand Up @@ -77,7 +77,7 @@ repos:
rev: v1.11.2
hooks:
- id: mypy
additional_dependencies: [pygeoif>=1.4, arrow, pytest]
additional_dependencies: [pygeoif>=1.4, arrow, pytest, hypothesis]
- repo: https://github.com/adamchainz/blacken-docs
rev: "1.18.0"
hooks:
Expand Down
4 changes: 2 additions & 2 deletions docs/usage_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Example how to build a simple KML file from the Python interpreter.
>>> f2.append(p)
# Print out the KML Object as a string
>>> print(k.to_string(prettyprint=True))
>>> print(k.to_string(prettyprint=True, precision=6))
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document id="docid">
<name>doc name</name>
Expand Down Expand Up @@ -145,7 +145,7 @@ You can create a KML object by reading a KML file as a string
>>> k.features[0].features[1].name = "ANOTHER NAME"
# Verify that we can print back out the KML object as a string
>>> print(k.to_string(prettyprint=True))
>>> print(k.to_string(prettyprint=True, precision=6))
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Document.kml</name>
Expand Down
1 change: 1 addition & 0 deletions fastkml/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ def __bool__(self) -> bool:
classes=(int,),
get_kwarg=attribute_int_kwarg,
set_element=int_attribute,
default=2,
),
)

Expand Down
77 changes: 53 additions & 24 deletions fastkml/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@

MsgMutualExclusive: Final = "Geometry and kml coordinates are mutually exclusive"

xml_attrs = {"ns", "name_spaces", "id", "target_id"}


def handle_invalid_geometry_error(
*,
Expand Down Expand Up @@ -159,15 +161,14 @@ def coordinates_subelement(
"""
if getattr(obj, attr_name, None):
p = precision if precision is not None else 6
coords = getattr(obj, attr_name)
if len(coords[0]) == 2: # noqa: PLR2004
tuples = (f"{c[0]:.{p}f},{c[1]:.{p}f}" for c in coords)
elif len(coords[0]) == 3: # noqa: PLR2004
tuples = (f"{c[0]:.{p}f},{c[1]:.{p}f},{c[2]:.{p}f}" for c in coords)
else:
if not coords or len(coords[0]) not in (2, 3):
msg = f"Invalid dimensions in coordinates '{coords}'"
raise KMLWriteError(msg)
if precision is None:
tuples = (",".join(str(c) for c in coord) for coord in coords)
else:
tuples = (",".join(f"{c:.{precision}f}" for c in coord) for coord in coords)
element.text = " ".join(tuples)


Expand Down Expand Up @@ -488,6 +489,21 @@ def __bool__(self) -> bool:
"""
return bool(self.geometry)

def __eq__(self, other: object) -> bool:
"""Check if the Point objects are equal."""
if isinstance(other, Point):
return all(
getattr(self, attr) == getattr(other, attr)
for attr in (
"extrude",
"altitude_mode",
"geometry",
*xml_attrs,
*self._get_splat(),
)
)
return super().__eq__(other)

@property
def geometry(self) -> Optional[geo.Point]:
"""
Expand Down Expand Up @@ -628,6 +644,21 @@ def __bool__(self) -> bool:
"""
return bool(self.geometry)

def __eq__(self, other: object) -> bool:
"""Check if the LineString objects is equal."""
if isinstance(other, LineString):
return all(
getattr(self, attr) == getattr(other, attr)
for attr in (
"extrude",
"tessellate",
"geometry",
*xml_attrs,
*self._get_splat(),
)
)
return super().__eq__(other)

@property
def geometry(self) -> Optional[geo.LineString]:
"""
Expand Down Expand Up @@ -754,22 +785,6 @@ def __init__(
**kwargs,
)

def __repr__(self) -> str:
"""Create a string (c)representation for LinearRing."""
return (
f"{self.__class__.__module__}.{self.__class__.__name__}("
f"ns={self.ns!r}, "
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"**{self._get_splat()!r},"
")"
)

@property
def geometry(self) -> Optional[geo.LinearRing]:
"""
Expand Down Expand Up @@ -1182,12 +1197,26 @@ def __repr__(self) -> str:
f"extrude={self.extrude!r}, "
f"tessellate={self.tessellate!r}, "
f"altitude_mode={self.altitude_mode}, "
f"outer_boundary={self.outer_boundary!r}, "
f"inner_boundaries={self.inner_boundaries!r}, "
f"geometry={self.geometry!r}, "
f"**{self._get_splat()!r},"
")"
)

def __eq__(self, other: object) -> bool:
"""Check if the Polygon objects are equal."""
if isinstance(other, Polygon):
return all(
getattr(self, attr) == getattr(other, attr)
for attr in (
"extrude",
"tessellate",
"geometry",
*xml_attrs,
*self._get_splat(),
)
)
return super().__eq__(other)


registry.register(
Polygon,
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ lxml = [
"lxml",
]
tests = [
"hypothesis",
"pytest",
"pytest-cov",
]
Expand Down Expand Up @@ -117,6 +118,7 @@ source = [
[tool.coverage.report]
exclude_also = [
"^\\s*\\.\\.\\.$",
"class \\w+\\(Protocol\\)\\:",
"except AssertionError:",
"except ImportError:",
"if TYPE_CHECKING:",
Expand Down
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Configure the tests.
Register the hypothesis 'exhaustive' profile to run 10 thousand examples.
Run this profile with ``pytest --hypothesis-profile=exhaustive``
"""

from hypothesis import HealthCheck
from hypothesis import settings

settings.register_profile(
"exhaustive",
max_examples=10_000,
suppress_health_check=[HealthCheck.too_slow],
)
settings.register_profile(
"coverage",
max_examples=10,
suppress_health_check=[HealthCheck.too_slow],
)
settings.register_profile("ci", suppress_health_check=[HealthCheck.too_slow])
4 changes: 2 additions & 2 deletions tests/geometries/boundaries_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_outer_boundary(self) -> None:
)

assert outer_boundary.geometry == geo.LinearRing(coords)
assert outer_boundary.to_string(prettyprint=False).strip() == (
assert outer_boundary.to_string(prettyprint=False, precision=6).strip() == (
'<kml:outerBoundaryIs xmlns:kml="http://www.opengis.net/kml/2.2">'
"<kml:LinearRing><kml:coordinates>"
"1.000000,2.000000 2.000000,0.000000 0.000000,0.000000 1.000000,2.000000"
Expand Down Expand Up @@ -79,7 +79,7 @@ def test_inner_boundary(self) -> None:

assert inner_boundary.geometry == geo.LinearRing(coords)
assert bool(inner_boundary)
assert inner_boundary.to_string(prettyprint=False).strip() == (
assert inner_boundary.to_string(prettyprint=False, precision=6).strip() == (
'<kml:innerBoundaryIs xmlns:kml="http://www.opengis.net/kml/2.2">'
"<kml:LinearRing><kml:coordinates>"
"1.000000,2.000000 2.000000,0.000000 0.000000,0.000000 1.000000,2.000000"
Expand Down
2 changes: 1 addition & 1 deletion tests/geometries/coordinates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_coordinates(self) -> None:

coordinates = Coordinates(coords=coords)

assert coordinates.to_string().strip() == (
assert coordinates.to_string(precision=6).strip() == (
'<kml:coordinates xmlns:kml="http://www.opengis.net/kml/2.2">'
"0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000"
Expand Down
60 changes: 33 additions & 27 deletions tests/geometries/geometry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ def test_create_kml_geometry_point(self) -> None:
"coordinates": (0.0, 1.0),
}
assert "Point>" in g.to_string()
assert "coordinates>0.000000,1.000000</" in g.to_string()
assert "coordinates>0.000000,1.000000</" in g.to_string(precision=6)

def test_create_kml_geometry_linestring(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -509,7 +509,9 @@ def test_create_kml_geometry_linestring(self) -> None:
"coordinates": ((0.0, 0.0), (1.0, 1.0)),
}
assert "LineString>" in g.to_string()
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in g.to_string()
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in g.to_string(
precision=6,
)

def test_create_kml_geometry_linearring(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -526,7 +528,7 @@ def test_create_kml_geometry_linearring(self) -> None:
assert (
"coordinates>0.000000,0.000000 1.000000,1.000000 1.000000,0.000000 "
"0.000000,0.000000</"
) in g.to_string()
) in g.to_string(precision=6)

def test_create_kml_geometry_polygon(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -543,7 +545,7 @@ def test_create_kml_geometry_polygon(self) -> None:
assert (
"coordinates>0.000000,0.000000 1.000000,1.000000 1.000000,0.000000 "
"0.000000,0.000000</"
) in g.to_string()
) in g.to_string(precision=6)

def test_create_kml_geometry_multipoint(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -552,12 +554,13 @@ def test_create_kml_geometry_multipoint(self) -> None:
assert isinstance(g, MultiGeometry)
assert g.geometry
assert len(g.geometry) == 4
assert "MultiGeometry>" in g.to_string()
assert "Point>" in g.to_string()
assert "coordinates>0.000000,0.000000</" in g.to_string()
assert "coordinates>1.000000,1.000000</" in g.to_string()
assert "coordinates>1.000000,0.000000</" in g.to_string()
assert "coordinates>2.000000,2.000000</" in g.to_string()
xml = g.to_string(precision=6)
assert "MultiGeometry>" in xml
assert "Point>" in xml
assert "coordinates>0.000000,0.000000</" in xml
assert "coordinates>1.000000,1.000000</" in xml
assert "coordinates>1.000000,0.000000</" in xml
assert "coordinates>2.000000,2.000000</" in xml

def test_create_kml_geometry_multilinestring(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -568,10 +571,11 @@ def test_create_kml_geometry_multilinestring(self) -> None:
assert isinstance(g, MultiGeometry)
assert g.geometry
assert len(g.geometry) == 2
assert "MultiGeometry>" in g.to_string()
assert "LineString>" in g.to_string()
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in g.to_string()
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in g.to_string()
xml = g.to_string(precision=6)
assert "MultiGeometry>" in xml
assert "LineString>" in xml
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in xml
assert "coordinates>0.000000,0.000000 1.000000,1.000000</" in xml

def test_create_kml_geometry_multipolygon(self) -> None:
"""Test the create_kml_geometry function."""
Expand All @@ -590,20 +594,21 @@ 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()
xml = g.to_string(precision=6)
assert "MultiGeometry>" in xml
assert "Polygon>" in xml
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000</"
) in g.to_string()
) in xml
assert (
"coordinates>0.100000,0.100000 0.100000,0.200000 0.200000,0.200000 "
"0.200000,0.100000 0.100000,0.100000</"
) in g.to_string()
) in xml
assert (
"coordinates>0.000000,0.000000 0.000000,1.000000 1.000000,1.000000 "
"1.000000,0.000000 0.000000,0.000000</"
) in g.to_string()
) in xml

def test_create_kml_geometry_geometrycollection(self) -> None:
multipoint = geo.MultiPoint([(0, 0), (1, 1), (1, 2), (2, 2)])
Expand All @@ -625,14 +630,15 @@ def test_create_kml_geometry_geometrycollection(self) -> None:
assert isinstance(g, MultiGeometry)
assert g.geometry
assert len(g.geometry) == 7
assert "MultiGeometry>" in g.to_string()
assert "LineString>" in g.to_string()
assert "LinearRing>" in g.to_string()
assert "Polygon>" in g.to_string()
assert "outerBoundaryIs>" in g.to_string()
assert "innerBoundaryIs>" in g.to_string()
assert "Point>" in g.to_string()
assert "coordinates>0.000000,0.000000</" in g.to_string()
xml = g.to_string(precision=6)
assert "MultiGeometry>" in xml
assert "LineString>" in xml
assert "LinearRing>" in xml
assert "Polygon>" in xml
assert "outerBoundaryIs>" in xml
assert "innerBoundaryIs>" in xml
assert "Point>" in xml
assert "coordinates>0.000000,0.000000</" in xml
assert g.geometry == gc

def test_create_kml_geometry_geometrycollection_roundtrip(self) -> None:
Expand Down
Loading

0 comments on commit 349436a

Please sign in to comment.