Skip to content

Commit

Permalink
Merge branch 'develop' into pre-commit-ci-update-config
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder authored Oct 14, 2023
2 parents 2d3888b + f8ff74c commit f719021
Show file tree
Hide file tree
Showing 33 changed files with 4,507 additions and 710 deletions.
11 changes: 6 additions & 5 deletions .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand All @@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand All @@ -46,7 +46,7 @@ jobs:
run: |
pytest tests --cov=fastkml --cov=tests --cov-fail-under=88 --cov-report=xml
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true

Expand All @@ -73,7 +73,8 @@ jobs:
run: |
flake8 fastkml examples docs
black --check fastkml examples docs
yamllint .github/workflows/
yamllint .github/
yamllint .*.y*ml
- name: Check complexity
run: |
radon cc --min B fastkml
Expand All @@ -84,7 +85,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
pypy-version: ['pypy-3.7', 'pypy-3.8', 'pypy-3.9']
pypy-version: ['pypy-3.7', 'pypy-3.8', 'pypy-3.9', 'pypy-3.10']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.pypy-version }}
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ setup.cfg
# Installer logs
pip-log.txt

# Unit test / coverage reports
# Unit/static test / coverage reports
.coverage
.tox
.pytest_cache/
nosetests.xml
.ruff_cache/
monkeytype.sqlite3

# Translations
*.mo
Expand All @@ -51,6 +54,8 @@ venv
.mypy_cache/
.pyre/
.watchmanconfig
.pytype/


# misc
.dccache
22 changes: 21 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,30 @@ repos:
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.292'
hooks:
- id: ruff
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0 # Use the ref you want to point at
hooks:
- id: python-use-type-annotations
- id: python-check-blanket-type-ignore
- id: python-check-mock-methods
- id: python-no-log-warn
- id: python-use-type-annotations
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal
- id: text-unicode-replacement-char
- repo: https://github.com/mgedmin/check-manifest
rev: "0.49"
hooks:
- id: check-manifest
- id: check-manifest
# - repo: https://github.com/Lucas-C/pre-commit-hooks-markup
# rev: v1.0.1
# hooks:
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs Makefile
recursive-include tests *.py
recursive-include schema *.xsd
11 changes: 10 additions & 1 deletion docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ especially in the following ways:
Pull Requests
-------------

Start by submitting a pull request on GitHub against the `develop` branch of the
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.

Expand Down Expand Up @@ -64,6 +64,15 @@ available.

.. _tox: https://pypi.python.org/pypi/tox

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


pre-commit
~~~~~~~~~~~

Expand Down
17 changes: 13 additions & 4 deletions fastkml/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

from fastkml.base import _XMLObject
from fastkml.config import ATOMNS as NS
from fastkml.enums import Verbosity
from fastkml.helpers import o_from_attr
from fastkml.helpers import o_from_subelement_text
from fastkml.helpers import o_int_from_attr
Expand Down Expand Up @@ -167,8 +168,12 @@ def __init__(
def from_element(self, element: Element) -> None:
super().from_element(element)

def etree_element(self) -> Element:
return super().etree_element()
def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
return super().etree_element(precision=precision, verbosity=verbosity)


class _Person(_XMLObject):
Expand Down Expand Up @@ -227,8 +232,12 @@ def __init__(
self.uri = uri
self.email = email

def etree_element(self) -> Element:
return super().etree_element()
def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
return super().etree_element(precision=precision, verbosity=verbosity)

def from_element(self, element: Element) -> None:
super().from_element(element)
Expand Down
160 changes: 152 additions & 8 deletions fastkml/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

"""Abstract base classes"""
import logging
from typing import Any
from typing import Dict
from typing import Optional
from typing import Tuple
from typing import cast

from fastkml import config
from fastkml.enums import Verbosity
from fastkml.helpers import o_from_attr
from fastkml.helpers import o_to_attr
from fastkml.types import Element
Expand All @@ -32,14 +35,36 @@
class _XMLObject:
"""XML Baseclass."""

_namespaces: Tuple[str, ...] = ("",)
_node_name: str = ""
__name__ = ""
kml_object_mapping: Tuple[KmlObjectMap, ...] = ()

def __init__(self, ns: Optional[str] = None) -> None:
"""Initialize the XML Object."""
self.ns: str = config.KMLNS if ns is None else ns
self.ns: str = self._namespaces[0] if ns is None else ns

def etree_element(self) -> Element:
def __eq__(self, other: object) -> bool:
"""Compare two XML Objects."""
if not isinstance(other, self.__class__):
return False
return (
other.ns == self.ns or other.ns in self._namespaces
if self.ns == ""
else True
)

def __repr__(self) -> str:
return f"{self.__class__.__name__}(ns={self.ns})"

def __str__(self) -> str:
return self.to_string()

def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
"""Return the KML Object as an Element."""
if self.__name__:
element: Element = config.etree.Element( # type: ignore[attr-defined]
Expand All @@ -54,25 +79,44 @@ def etree_element(self) -> Element:
return element

def from_element(self, element: Element) -> None:
"""Load the KML Object from an Element."""
"""
Load the KML Object from an Element.
This implementation is deprecated and will be replaced by class_from_element
making it a classmethod.
"""
if f"{self.ns}{self.__name__}" != element.tag:
raise TypeError("Call of abstract base class, subclasses implement this!")
for mapping in self.kml_object_mapping:
mapping["from_kml"](self, element, **mapping)

def from_string(self, xml_string: str) -> None:
"""Load the KML Object from serialized xml."""
"""
Load the KML Object from serialized xml.
This implementation is deprecated and will be replaced by class_from_string
making it a classmethod.
"""
self.from_element(
cast(Element, config.etree.XML(xml_string)) # type: ignore[attr-defined]
)

def to_string(self, prettyprint: bool = True) -> str:
def to_string(
self,
*,
prettyprint: bool = True,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> str:
"""Return the KML Object as serialized xml."""
try:
return cast(
str,
config.etree.tostring( # type: ignore[attr-defined]
self.etree_element(),
self.etree_element(
precision=precision,
verbosity=verbosity,
),
encoding="UTF-8",
pretty_print=prettyprint,
).decode("UTF-8"),
Expand All @@ -85,6 +129,63 @@ def to_string(self, prettyprint: bool = True) -> str:
).decode("UTF-8"),
)

@classmethod
def _get_ns(cls, ns: Optional[str]) -> str:
return cls._namespaces[0] if ns is None else ns

@classmethod
def _get_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
"""Returns a dictionary of kwargs for the class constructor."""
kwargs: Dict[str, Any] = {}
return kwargs

@classmethod
def class_from_element(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> "_XMLObject":
"""Creates an XML object from an etree element."""
kwargs = cls._get_kwargs(ns=ns, element=element, strict=strict)
return cls(
ns=ns,
**kwargs,
)

@classmethod
def class_from_string(
cls,
string: str,
*,
ns: Optional[str] = None,
strict: bool = True,
) -> "_XMLObject":
"""Creates a geometry object from a string.
Args:
string: String representation of the geometry object
Returns:
Geometry object
"""
ns = cls._get_ns(ns)
return cls.class_from_element(
ns=ns,
strict=strict,
element=cast(
Element,
config.etree.fromstring(string), # type: ignore[attr-defined]
),
)


class _BaseObject(_XMLObject):
"""
Expand All @@ -98,6 +199,9 @@ class _BaseObject(_XMLObject):
mechanism is to be used.
"""

_namespace = config.KMLNS
_namespaces: Tuple[str, ...] = (config.KMLNS,)

id = None
target_id = None
kml_object_mapping: Tuple[KmlObjectMap, ...] = (
Expand Down Expand Up @@ -130,10 +234,50 @@ def __init__(
self.id = id
self.target_id = target_id

def etree_element(self) -> Element:
def __eq__(self, other: object) -> bool:
assert isinstance(other, self.__class__)
return (
super().__eq__(other)
and self.id == other.id
and self.target_id == other.target_id
)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(ns={self.ns!r}, "
f"(id={self.id!r}, target_id={self.target_id!r})"
)

def etree_element(
self,
precision: Optional[int] = None,
verbosity: Verbosity = Verbosity.normal,
) -> Element:
"""Return the KML Object as an Element."""
return super().etree_element()
return super().etree_element(precision=precision, verbosity=verbosity)

def from_element(self, element: Element) -> None:
"""Load the KML Object from an Element."""
super().from_element(element)

@classmethod
def _get_id(cls, element: Element, strict: bool) -> str:
return element.get("id") or ""

@classmethod
def _get_target_id(cls, element: Element, strict: bool) -> str:
return element.get("targetId") or ""

@classmethod
def _get_kwargs(
cls,
*,
ns: str,
element: Element,
strict: bool,
) -> Dict[str, Any]:
"""Get the keyword arguments to build the object from an element."""
return {
"id": cls._get_id(element=element, strict=strict),
"target_id": cls._get_target_id(element=element, strict=strict),
}
Loading

0 comments on commit f719021

Please sign in to comment.