diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9947b93c..d3629c79 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -43,7 +43,7 @@ repos:
hooks:
- id: absolufy-imports
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: 'v0.7.3'
+ rev: 'v0.7.4'
hooks:
- id: ruff
- id: ruff-format
diff --git a/README.rst b/README.rst
index 4c8f58b2..3fe99453 100644
--- a/README.rst
+++ b/README.rst
@@ -116,7 +116,7 @@ Optional
.. code-block:: bash
- pip install --pre "fastkml[lxml]"
+ pip install "fastkml[lxml]"
Limitations
===========
diff --git a/docs/co2growth.gif b/docs/co2growth.gif
new file mode 100644
index 00000000..9f84a28d
Binary files /dev/null and b/docs/co2growth.gif differ
diff --git a/docs/create_kml_files.rst b/docs/create_kml_files.rst
index e81a5538..f722aa20 100644
--- a/docs/create_kml_files.rst
+++ b/docs/create_kml_files.rst
@@ -6,6 +6,14 @@ Read a shapefile and build a 3D KML visualization.
This example shows how to read a shapefile and build a 3D KML visualization from it.
+.. image:: co2-per-capita-2020.jpg
+ :alt: CO2 emissions per capita in 2020
+ :align: center
+ :width: 800px
+ :target: https://ion.cesium.com/stories/viewer/?id=a3cf93bb-bbb8-488b-8643-09c037ec12b8
+
+(click on the image to see the KML visualization in Cesium Ion)
+
You will need to install `pyshp `_ (``pip install pyshp``).
For this example we will use the
@@ -108,12 +116,6 @@ Finally, we create the KML object and write it to a file:
The resulting KML file can be opened in Google Earth or any other KML viewer.
-.. image:: co2-per-capita-2020.jpg
- :alt: CO2 emissions per capita in 2020
- :align: center
- :width: 800px
- :target: https://ion.cesium.com/stories/viewer/?id=a3cf93bb-bbb8-488b-8643-09c037ec12b8
-
Build an animated over time KML visualization
----------------------------------------------
@@ -122,6 +124,14 @@ This example shows how to build an animated KML visualization over time.
We will use the same data as in the previous example, but this time we will
create a KML file that shows the CO2 emissions accumulating from 1995 to 2022.
+.. image:: co2growth.gif
+ :alt: CO2 emissions per capita growth
+ :align: center
+ :width: 800px
+ :target: https://ion.cesium.com/stories/viewer/?id=602c8c64-72aa-4c57-8a01-752b6fbc62d0
+
+(click on the image to see the full visualization in Cesium Ion)
+
First we import the necessary modules:
.. code-block:: pycon
diff --git a/docs/fastkml.rst b/docs/fastkml.rst
index e752b965..96553566 100644
--- a/docs/fastkml.rst
+++ b/docs/fastkml.rst
@@ -4,27 +4,76 @@ Reference Guide
.. automodule:: fastkml
+fastkml.kml
+------------------
-fastkml.about
---------------------
-
-.. automodule:: fastkml.about
+.. automodule:: fastkml.kml
:members:
:undoc-members:
:show-inheritance:
-fastkml.atom
+
+fastkml.base
-------------------
-.. automodule:: fastkml.atom
+.. automodule:: fastkml.base
:members:
:undoc-members:
:show-inheritance:
-fastkml.base
+.. autoclass:: fastkml.base._XMLObject
+ :members: etree_element, _get_kwargs, to_string, from_string, validate, __init__
+ :undoc-members:
+ :show-inheritance:
+
+fastkml.registry
+-----------------------
+
+.. automodule:: fastkml.registry
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: fastkml.registry::Registry
+ :members: register, get
+ :undoc-members:
+ :show-inheritance:
+
+fastkml.kml\_base
+------------------------
+
+.. automodule:: fastkml.kml_base
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: fastkml.kml_base::_BaseObject
+ :members: __init__
+ :undoc-members:
+ :show-inheritance:
+
+fastkml.helpers
+----------------------
+
+.. automodule:: fastkml.helpers
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+fastkml.about
+--------------------
+
+.. automodule:: fastkml.about
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+ .. autodata:: __version__
+
+fastkml.atom
-------------------
-.. automodule:: fastkml.base
+.. automodule:: fastkml.atom
:members:
:undoc-members:
:show-inheritance:
@@ -93,30 +142,6 @@ fastkml.gx
:undoc-members:
:show-inheritance:
-fastkml.helpers
-----------------------
-
-.. automodule:: fastkml.helpers
- :members:
- :undoc-members:
- :show-inheritance:
-
-fastkml.kml
-------------------
-
-.. automodule:: fastkml.kml
- :members:
- :undoc-members:
- :show-inheritance:
-
-fastkml.kml\_base
-------------------------
-
-.. automodule:: fastkml.kml_base
- :members:
- :undoc-members:
- :show-inheritance:
-
fastkml.links
--------------------
@@ -141,13 +166,6 @@ fastkml.overlays
:undoc-members:
:show-inheritance:
-fastkml.registry
------------------------
-
-.. automodule:: fastkml.registry
- :members:
- :undoc-members:
- :show-inheritance:
fastkml.styles
---------------------
diff --git a/fastkml/about.py b/fastkml/about.py
index 578573fc..ce656dad 100644
--- a/fastkml/about.py
+++ b/fastkml/about.py
@@ -19,6 +19,7 @@
The only purpose of this module is to provide a version number for the package.
"""
-__version__ = "1.0.0rc2"
+__version__ = "1.0.0"
+"""Fastkml version number."""
__all__ = ["__version__"]
diff --git a/fastkml/base.py b/fastkml/base.py
index 88e7c0dd..ca2c7c21 100644
--- a/fastkml/base.py
+++ b/fastkml/base.py
@@ -14,7 +14,22 @@
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""Abstract base classes."""
+"""
+Abstract XML base class.
+
+The purpose of ``_XMLObject`` is to serve as a base class for KML objects in fastkml.
+Its main functions are:
+
+- Provide a common interface for XML serialization and deserialization.
+- Handle namespace management for KML elements.
+- Manage attribute storage and retrieval for derived classes.
+- Provide the ``etree_element()`` method for converting objects to XML Elements.
+- Facilitate integration with the registry system for flexible XML mapping.
+
+By inheriting from ``_XMLObject``, KML classes gain these capabilities, ensuring
+consistent handling of XML operations across the library.
+
+"""
import logging
from typing import Any
@@ -127,6 +142,18 @@ def etree_element(
"""
Return the KML Object as an Element.
+ This method essentially converts the Python object to its XML representation,
+ using the registry to determine how each attribute should be serialized.
+
+ - Create an XML Element with the object's tag name and namespace.
+ - Iterate through registered attributes for the object's class.
+ For each attribute:
+ - Call the corresponding set_element function. This function adds the
+ attribute to the Element as a sub-element or attribute.
+ - Handle different data types and nested objects.
+ - Apply precision and verbosity settings if specified.
+ - Return the complete Element tree representing the object.
+
Parameters
----------
precision : Optional[int], default=None
@@ -280,6 +307,31 @@ def _get_kwargs(
"""
Get the keyword arguments for the class constructor.
+ A class method used for XML deserialization. Its main purposes are:
+
+ - Extract attribute values from an XML element.
+ - Convert these values into appropriate Python types.
+ - Prepare a dictionary of keyword arguments for object initialization.
+ - It is called during the parsing process to populate object attributes from XML
+ data. The method uses the registry and helper functions to handle different
+ attribute types and nested objects.
+
+ Subclasses may override this method to add custom deserialization logic for
+ specific KML elements, although this should be rare. Prefer registration over
+ a custom ``_get_kwargs`` implementation to ensure consistent handling of KML:
+
+ - Consistency: Ensures uniform handling across all KML elements.
+ - Maintainability: Centralizes parsing logic, making updates easier.
+ - Declarative approach: Simplifies code by moving logic to configuration.
+ - Reusability: Allows sharing of parsing logic across different classes.
+ - Separation of concerns: Keeps parsing logic separate from class definitions.
+ - Extensibility: Makes it easier to add new attributes or change parsing
+ behavior.
+ - Reduced duplication: Avoids repeating similar parsing code in multiple
+ classes.
+ - Easier testing: Allows testing of parsing logic independently of class
+ implementations.
+
Parameters
----------
ns : str
diff --git a/fastkml/enums.py b/fastkml/enums.py
index 75de9ebb..df3f22e3 100644
--- a/fastkml/enums.py
+++ b/fastkml/enums.py
@@ -26,6 +26,22 @@
from enum import Enum
from enum import unique
+__all__ = [
+ "AltitudeMode",
+ "ColorMode",
+ "DataType",
+ "DateTimeResolution",
+ "DisplayMode",
+ "GridOrigin",
+ "PairKey",
+ "RefreshMode",
+ "RelaxedEnum",
+ "Shape",
+ "Units",
+ "Verbosity",
+ "ViewRefreshMode",
+]
+
logger = logging.getLogger(__name__)
@@ -40,14 +56,15 @@ class RelaxedEnum(Enum):
Usage:
To use this enum, simply subclass `RelaxedEnum` and define your enum values.
- Example:
- -------
- class MyEnum(RelaxedEnum):
- VALUE1 = "value1"
- VALUE2 = "value2"
+ Example::
+
+ class AltitudeMode(RelaxedEnum):
+ clamp_to_ground = "clampToGround"
+ relative_to_ground = "relativeToGround"
+ absolute = "absolute"
- my_value = MyEnum("VALUE1") # Case-insensitive match
- print(my_value) # Output: MyEnum.VALUE1
+ my_value = AltitudeMode("CLAMPTOGROUND") # Case-insensitive match
+ print(my_value) # Output: AltitudeMode.clamp_to_ground
The subclass must define all values as strings.
@@ -102,33 +119,33 @@ class AltitudeMode(RelaxedEnum):
Possible values are
- clampToGround - (default) Indicates to ignore an altitude specification
- (for example, in the tag).
+ (for example, in the tag).
- relativeToGround - Sets the altitude of the element relative to the actual
- ground elevation of a particular location.
- For example, if the ground elevation of a location is exactly at sea level
- and the altitude for a point is set to 9 meters,
- then the elevation for the icon of a point placemark elevation is 9 meters
- with this mode.
- However, if the same coordinate is set over a location where the ground
- elevation is 10 meters above sea level, then the elevation of the coordinate
- is 19 meters.
- A typical use of this mode is for placing telephone poles or a ski lift.
+ ground elevation of a particular location.
+ For example, if the ground elevation of a location is exactly at sea level
+ and the altitude for a point is set to 9 meters,
+ then the elevation for the icon of a point placemark elevation is 9 meters
+ with this mode.
+ However, if the same coordinate is set over a location where the ground
+ elevation is 10 meters above sea level, then the elevation of the coordinate
+ is 19 meters.
+ A typical use of this mode is for placing telephone poles or a ski lift.
- absolute - Sets the altitude of the coordinate relative to sea level,
- regardless of the actual elevation of the terrain beneath the element.
- For example, if you set the altitude of a coordinate to 10 meters with an
- absolute altitude mode, the icon of a point placemark will appear to be at
- ground level if the terrain beneath is also 10 meters above sea level.
- If the terrain is 3 meters above sea level, the placemark will appear elevated
- above the terrain by 7 meters.
- A typical use of this mode is for aircraft placement.
+ regardless of the actual elevation of the terrain beneath the element.
+ For example, if you set the altitude of a coordinate to 10 meters with an
+ absolute altitude mode, the icon of a point placemark will appear to be at
+ ground level if the terrain beneath is also 10 meters above sea level.
+ If the terrain is 3 meters above sea level, the placemark will appear elevated
+ above the terrain by 7 meters.
+ A typical use of this mode is for aircraft placement.
- relativeToSeaFloor - Interprets the altitude as a value in meters above the
- sea floor.
- If the point is above land rather than sea, the altitude will be interpreted
- as being above the ground.
+ sea floor.
+ If the point is above land rather than sea, the altitude will be interpreted
+ as being above the ground.
- clampToSeaFloor - The altitude specification is ignored, and the point will be
- positioned on the sea floor.
- If the point is on land rather than at sea, the point will be positioned on
- the ground.
+ positioned on the sea floor.
+ If the point is on land rather than at sea, the point will be positioned on
+ the ground.
The Values relativeToSeaFloor and clampToSeaFloor are not part of the KML definition
but of the a KML extension in the Google extension namespace,
diff --git a/fastkml/helpers.py b/fastkml/helpers.py
index 85a888e8..22010293 100644
--- a/fastkml/helpers.py
+++ b/fastkml/helpers.py
@@ -13,7 +13,22 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""Helper functions for fastkml."""
+"""
+Helper functions for fastkml.
+
+The helpers module acts as a bridge between the abstract registry definitions and the
+concrete XML operations performed by ``_XMLObject``.
+
+- Provides utility functions for XML parsing and serialization referenced in the
+ registry for handling different data types.
+- Implements ``get_kwarg`` and ``set_element`` functions used by ``_XMLObject``.
+- Offers helper functions for common operations like text handling and type conversions.
+
+``_XMLObject`` uses these helpers indirectly through the registry.
+The registry references these helper functions for attribute parsing and serialization.
+They form the implementation layer for the declarative approach defined by the registry.
+
+"""
import logging
from enum import Enum
diff --git a/fastkml/kml.py b/fastkml/kml.py
index a0314c11..0886c6ba 100644
--- a/fastkml/kml.py
+++ b/fastkml/kml.py
@@ -153,12 +153,10 @@ def etree_element(
Return an Element object representing the KML element.
Args:
- ----
precision (Optional[int]): The precision used for floating-point values.
verbosity (Verbosity): The verbosity level for generating the KML element.
Returns:
- -------
Element: The etree Element object representing the KML element.
"""
@@ -211,14 +209,11 @@ def parse(
Parse a KML file and return a KML object.
Args:
- ----
- file: The file to parse.
- Can be a file path (str or Path), or a file-like object.
+ file: The file to parse. Can be a file path or a file-like object.
Keyword Args:
- ------------
ns (Optional[str]): The namespace of the KML file.
- If not provided, it will be inferred from the root element.
+ If not provided, it will be inferred from the root element.
name_spaces (Optional[Dict[str, str]]): Additional namespaces.
strict (bool): Whether to enforce strict parsing rules. Defaults to True.
validate (Optional[bool]): Whether to validate the file against the schema.
@@ -257,10 +252,9 @@ def write(
Write KML to a file.
Args:
- ----
file_path: The file name where to save the file.
Can be any string value
- prettyprint : bool, default=True
+ prettyprint (bool): default=True
Whether to pretty print the XML.
precision (Optional[int]): The precision used for floating-point values.
verbosity (Verbosity): The verbosity level for generating the KML element.
diff --git a/fastkml/kml_base.py b/fastkml/kml_base.py
index adaa5cad..3e86bf25 100644
--- a/fastkml/kml_base.py
+++ b/fastkml/kml_base.py
@@ -14,7 +14,21 @@
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""Abstract base classes."""
+"""
+Abstract KML base class.
+
+It adapts the generic XML functionality of ``_XMLObject`` to the specific needs of KML
+elements, providing a foundation for all KML-specific classes in fastkml.
+
+- Serve as the base class for all KML objects
+- Implement common KML attributes like 'id' and 'target_id'
+- Set the default namespace to KML
+- Provide KML-specific initialization logic
+- Act as an intermediary between _XMLObject and concrete KML classes
+- Ensure consistent handling of basic KML properties across all elements
+- Facilitate KML-specific functionality while inheriting generic XML capabilities
+
+"""
from typing import Any
from typing import Dict
diff --git a/fastkml/registry.py b/fastkml/registry.py
index e7604908..96e53bb1 100644
--- a/fastkml/registry.py
+++ b/fastkml/registry.py
@@ -13,7 +13,22 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-"""Registry for XML objects."""
+"""
+Registry for XML objects.
+
+The Registry is used to store and retrieve information about XML objects.
+This approach allows for flexible, declarative mapping between XML and Python objects,
+with the registry acting as a central configuration for these mappings.
+
+Direct ``Registry`` class use is typically only for library internals or advanced
+customization. For normal usage, stick with the ``registry`` instance:
+
+- The library is designed around this global instance.
+- Ensures all parts of the library use the same registry.
+- Pre-populated with standard KML mappings.
+- Singleton pattern: Avoids multiple conflicting registries.
+
+"""
from dataclasses import dataclass
from typing import TYPE_CHECKING
@@ -63,7 +78,22 @@ def __call__(
@dataclass(frozen=True)
class RegistryItem:
- """A registry item."""
+ """
+ A registry item.
+
+ The RegistryItem class is a dataclass that represents a single mapping between an
+ XML object and a Python object. It contains the following fields:
+
+ - ``ns_ids``: A tuple of namespace identifiers that the mapping applies to.
+ - ``classes``: A tuple of Python classes that the mapping applies to.
+ - ``attr_name``: The name of the attribute on the Python object that corresponds to
+ the XML object.
+ - ``get_kwarg``: A function that retrieves keyword arguments for the Python object.
+ - ``type``: The type of the XML object.
+ - ``node_name``: The name of the XML node that the mapping applies to.
+ - ``default``: An optional default value for the Python object attribute.
+
+ """
ns_ids: Tuple[str, ...]
classes: Tuple[Type[object], ...]
@@ -75,7 +105,26 @@ class RegistryItem:
class Registry:
- """A registry of XML objects."""
+ """
+ A registry of XML objects.
+
+ The registry acts as a configuration hub, allowing the library to dynamically handle
+ various KML elements and their attributes without hardcoding the logic into each
+ class.
+
+ The purpose of the registry is to:
+
+ - Centralize XML mapping configuration for KML objects.
+ - Define attribute-to-element/attribute mappings.
+ - Specify parsing and serialization functions for each attribute.
+ - Support inheritance in XML mappings.
+ - Provide a flexible, declarative approach to XML handling.
+ - Decouple XML parsing/serialization logic from class definitions.
+ - Allow easy addition or modification of XML mappings.
+ - Enable consistent handling of attributes across different KML classes.
+ - Facilitate extensibility and maintainability of the library.
+
+ """
_registry: Dict[Type["_XMLObject"], List[RegistryItem]]
@@ -93,13 +142,43 @@ def __repr__(self) -> str:
)
def register(self, cls: Type["_XMLObject"], item: RegistryItem) -> None:
- """Register a class."""
+ """
+ Register a class.
+
+ Add a new RegistryItem to the registry for a specific class.
+
+ - Appends the item to an existing list if the class is already registered.
+ - Creates a new list with the item if it's the first for that class.
+ - Associates XML parsing/serialization rules with a class attribute.
+ - Defines how a specific attribute should be handled in XML operations.
+ - Allows for multiple registrations per class, supporting complex mappings.
+ - Is called during library initialization to set up KML mappings.
+
+ This is the primary way to configure how different KML elements and their
+ attributes are processed in fastkml.
+
+ """
existing = self._registry.get(cls, [])
existing.append(item)
self._registry[cls] = existing
def get(self, cls: Type["_XMLObject"]) -> List[RegistryItem]:
- """Get a class by name."""
+ """
+ Get the registry items for a class and its ancestors.
+
+ The get method of the registry, in conjunction with _XMLObject:
+
+ - Retrieves all registered items for a given class and its ancestors.
+ - Supports inheritance in XML mappings.
+ - Allows ``_XMLObject`` to dynamically determine how to parse/serialize
+ attributes.
+ - Enables flexible XML handling without hardcoding in each class.
+ - Facilitates polymorphic behavior in XML parsing and serialization.
+
+ It allows ``_XMLObject`` to handle different KML elements consistently while
+ respecting their inheritance structure.
+
+ """
parents = reversed(cls.__mro__[:-1])
items = []
for parent in parents:
diff --git a/pyproject.toml b/pyproject.toml
index 674d2541..c525ae04 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ authors = [
{ email = "christian.ledermann@gmail.com", name = "Christian Ledermann" },
]
classifiers = [
- "Development Status :: 4 - Beta",
+ "Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
@@ -179,6 +179,7 @@ include = [
[tool.rstcheck]
ignore_directives = [
+ "autoclass",
"automodule",
]
report_level = "WARNING"
@@ -227,15 +228,6 @@ select = [
"S101",
"SLF001",
]
-"tests/oldunit_test.py" = [
- "D100",
- "D200",
- "D401",
- "E501",
- "FIX",
- "ICN001",
- "TD",
-]
"tests/repr_eq_test.py" = [
"E501",
]
diff --git a/tests/repr_eq_test.py b/tests/repr_eq_test.py
index 8297258f..20b0eb70 100644
--- a/tests/repr_eq_test.py
+++ b/tests/repr_eq_test.py
@@ -1918,7 +1918,6 @@ def test_eq_str_round_trip(self) -> None:
assert str(self.clean_doc) == str(new_doc)
assert new_doc == self.clean_doc
- # srict equality is not a given new_doc == self.clean_doc
class TestReprLxml(Lxml, TestRepr):