here.platform.adapter
Source code for here.platform.adapter
Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).
All rights reserved.
This software and other materials contain proprietary information
controlled by HERE and are protected by applicable copyright legislation.
Any use and utilization of this software and other materials and
disclosure to any third parties is conditional upon having a separate
agreement with HERE for the access, use, utilization or disclosure of this
software. In the absence of such agreement, the use of the software is not
allowed.
"""HERE platform, content adaptation, encoding and decoding."""
import dataclasses
from abc import ABC, abstractmethod
from datetime import datetime
from typing import (
TYPE_CHECKING,
Callable,
Dict,
Iterable,
Iterator,
NewType,
Optional,
Tuple,
Union,
)
import geojson
from geojson import LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon
from geojson.geometry import Geometry
from here.platform.exceptions import (
UnsupportedContentTypeDecodeException,
UnsupportedContentTypeEncodeException,
)
from here.platform.partition import (
IndexPartition,
StreamPartition,
VersionedPartition,
VolatilePartition,
)
from here.platform.schema import Schema
if TYPE_CHECKING:
from here.platform.layer import StreamLayer, VersionedLayer, VolatileLayer
[docs]
class Encoder(ABC):
"""A content encoder and its capabilities."""
@property
@abstractmethod
def supported_content_types(self) -> Dict[str, Union[type, Tuple[type, ...]]]:
"""
:return: the dictionary of MIME content types supported when encoding single blobs # noqa
with the encode_blob function of this encoder, each with the type of the encoded data.
"""
[docs]
@abstractmethod
def encode_blob(
self, data, content_type: str, schema: Optional[Schema] = None, **kwargs
) -> bytes:
"""Encode one single blob of data.
:param data: the data to be encoded, its type corresponds to the type declared in
the property supported_content_types for the content type
:param content_type: the MIME content type to be encoded
:param schema: the schema, if the content type requires one
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: the encoded data # noqa
:raises UnsupportedContentTypeEncodeException: in case the content type is not encodable
:raises ValueError: if the schema is mandatory for the content type but missing
:raises EncodeException: in case the blob can't be properly encoded # noqa
:raises SchemaException: in case the schema can't be used to encode the content # noqa
"""
def _verify_encode_blob(self, data, content_type: str, schema: Optional[Schema]):
if data is None:
raise ValueError("Data is not set")
if not content_type:
raise ValueError("Content type is not set")
if content_type not in self.supported_content_types:
raise UnsupportedContentTypeEncodeException(content_type=content_type)
if schema is not None and content_type not in schema.supported_content_types:
raise ValueError(f"Schema does not support content type {content_type}")
[docs]
class Decoder(ABC):
"""A content decoder and its capabilities."""
@property
@abstractmethod
def supported_content_types(self) -> Dict[str, Union[type, Tuple[type, ...]]]:
"""
:return: the dictionary of MIME content types supported when decoding single blobs # noqa
with the decode_blob function of this decoder, each with the type of the decoded data.
"""
[docs]
@abstractmethod
def decode_blob(
self, data: bytes, content_type: str, schema: Optional[Schema] = None, **kwargs
):
"""Decode one single blob of data.
:param data: the encoded data
:param content_type: the MIME content type to be decoded
:param schema: the schema, if the content type requires one
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: the decoded blob, its type corresponds to the type declared in
the property supported_content_types for the content type # noqa
:raises UnsupportedContentTypeDecodeException: in case the content type is not decodable
:raises ValueError: if the schema is mandatory for the content type but missing
:raises DecodeException: in case the blob can't be properly decoded # noqa
:raises SchemaException: in case the schema can't be used to decode the content # noqa
"""
def _verify_decode_blob(self, data: bytes, content_type: str, schema: Optional[Schema]):
if not data:
raise ValueError("Data is not set or empty")
if not content_type:
raise ValueError("Content type is not set")
if content_type not in self.supported_content_types:
raise UnsupportedContentTypeDecodeException(content_type=content_type)
if schema is not None and content_type not in schema.supported_content_types:
raise ValueError(f"Schema does not support content type {content_type}")
def _verify_and_return_decoded(self, decoded, content_type: str):
assert isinstance(decoded, self.supported_content_types[content_type])
return decoded
Some types supported by the ContentAdapter interface.
These model single values of well-known types with additional, clear semantic.
These types can be used in data classes and in composite types below.
Partition = NewType("Partition", str)
Identifier = NewType("Identifier", str)
DecodedMessage = NewType("DecodedMessage", dict)
Specialized types and helper functions to instantiate them
[docs]
@dataclasses.dataclass(frozen=True)
class Ref:
"""A reference to an object in a partition."""
partition: Partition
identifier: Identifier
[docs]
@dataclasses.dataclass(frozen=True)
class Range:
"""A [0, 1] range."""
start: Union[float, None]
end: Union[float, None]
[docs]
def make_ref(partition: Union[str, int, Partition], identifier: Union[str, Identifier]) -> Ref:
"""
Construct a new Reference given partition and identifier.
This supports a variety of parameter types and performs basics checks.
:param partition: the partition name
:param identifier: the object or feature identifier
:return: a reference
:raises ValueError: in case of invalid or empty parameters
"""
if not partition:
raise ValueError("Partition parameter not set")
if not identifier:
raise ValueError("Identifier parameter not set")
return Ref(Partition(str(partition)), Identifier(identifier))
TODO (OLPSUP-18956) remove this method once admin place category data are referenced correctly
[docs]
def make_cat_ref(partition: Union[str, int, Partition], identifier: Union[str, Identifier]) -> Ref:
"""
Construct a new Category Reference given identifier and/or partition.
This supports a variety of parameter types and performs basics checks.
:param partition: the partition name
:param identifier: the object or feature identifier
:return: a reference
:raises ValueError: in case of invalid or empty parameters
"""
if not identifier:
raise ValueError("Identifier parameter not set")
return Ref(Partition(str(partition)), Identifier(identifier))
[docs]
def make_range(
start: Union[int, float, None],
end: Union[int, float, None],
) -> Range:
"""
Construct a new Range given start and end offsets.
This supports a variety of parameter types and performs basics checks.
:param start: the start offset, when available and greater or equal 0.0
:param end: the end offset, when available and less or equal 1.0.
:return: a range
:raises ValueError: in case of parameters out of range
"""
if start is None:
start_offset = start
else:
start_offset = float(start)
if not (0 <= start_offset <= 1):
raise ValueError(f"Start offset {start} must be in [0, 1] range")
if end is None:
end_offset = end
else:
end_offset = float(end)
if not (0 <= end_offset <= 1):
raise ValueError(f"End offset {end} must be in [0, 1] range")
Disabled because HMC violates this condition in some special cases
if start_offset > end_offset:
raise ValueError(f"Start offset {start} greater than end offset {end}")
return Range(start_offset, end_offset)
Content is described by Python data classes. Each field of a data class must
have one of the supported types. Type are not only used as hints but affect
the behavior of the software at runtime. Data classes are used to specify
the fields on the objects passed to from_objects.
from_objects.The hints describe the data classes provided to from_objects,
from_objects,they don't describe the output format. The output format is adapter-specific,
although based on the actual type of the field and its type hint.
Adapter implementations use type information to better represent each value,
handle partition and object id, validate the data and convert non-basic types
like ranges and geometries to the most appropriate format for the output.
It is important that partition and object identifiers are marked
with the corresponding type hint to enable automatic indexing.
supported_single_value_types = [
bool,
int,
float,
str,
datetime,
DecodedMessage,
Partition,
Identifier,
Ref,
Range,
Geometry,
Point,
MultiPoint,
LineString,
MultiLineString,
Polygon,
MultiPolygon,
]
The data class that specifies the content, as mentioned, should have fields of one
of the types mentioned above. In addition, composite types are supported,
although with some constraints:
- Optional[T], of a supported type T
- Dict[str, T], key must be str, elements must be of a supported type T
- List[T], elements must be of a supported type T
- a data class with supported fields
These and other constrains are checked at runtime and in tests.
[docs]
class ContentAdapter(ABC):
"""
Interface of the content adapters, an adapter specialized to
work with content via the here-content package.
"""
[docs]
@abstractmethod
def from_tabular(self, columns, data, geometry_column="geometry"):
"""
Convert the given tabular data.
:param columns: column names
:param data: tabular data
:param geometry_column: geometry column string
:return: data representation is function of the adapter # noqa
"""
[docs]
@abstractmethod
def from_objects(
self,
fields: type,
data: Iterator[object],
single_element: bool = False,
index_partition: Union[None, str, Callable[[object], Partition]] = None,
index_id: Union[None, str, Callable[[object], Identifier]] = None,
index_ref: Union[None, str, Callable[[object], Union[Ref, Iterable[Ref]]]] = None,
):
"""
Adapt content form an object representation to the target format.
It can optionally perform indexing of objects, based on their partition,
identifier and set of references to other objects. Indexing is specified by
naming the field of the object that contains the value to index, or by
passing a function that calculates that value from the object.
:param fields: the fields to extract, as specified by a dataclass. Field names are looked
up among the attributes of each object via getattr`. When missing, Noneor equivalent is used. Each field has a type that describes its semantic: it is used to adapt the value to the most appropriate representation for the output format.TypeErroris raised in case this is not possible. :param data: the objects to adapt to the target format. Fields not mentioned infields are discarded. Expected but missing fields and identifiers are consideredNone``.
Field values may be of any type compatible with the type declared for the field.
Partition ids don't have to be unique, but they have to be contiguous: all the objects
with a given partition identifier must be returned in sequence. Object identifiers,
when present, must be unique across the whole content.
:param single_element: the data contains exactly one element, the content adapter
case use this information to optimize or return a specialized representation
:param index_partition: index the content by partition
:param index_id: index the content by object identifier
:param index_ref: index the content by references. Each object can contain zero,
one or more references, and references can be shared among multiple objects.
:return: an adapter-specific representation of the structured content.
:raises:
ValueError: if the fields are not described by a dataclass
KeyError: in case partition id, object id or reference is needed but not present
TypeError: in case partition or object id is not of type int or string.
Also raised in case field values are not of the type declared for the field,
or if they can't be converted to it.
"""
The following are support functions shared across implementations
@staticmethod
def _validate_params(
single_element: bool,
index_partition: Union[None, str, Callable[[object], Partition]],
index_id: Union[None, str, Callable[[object], Identifier]],
index_ref: Union[None, str, Callable[[object], Union[Ref, Iterable[Ref]]]],
):
if single_element and index_ref:
raise ValueError(
"Single element and indexing by references "
"to other objects are mutually-exclusive"
)
if index_ref and (index_partition or index_id):
raise ValueError(
"Indexing by partition or object identifier and indexing by "
"references to other objects are mutually-exclusive"
)
@staticmethod
def _validate_fields(fields: type):
This check validates the constraints on the data class (fields)
as described by the comment block where supported_single_value_types
supported_single_value_typesand the types are defined, in adapter.py.
adapter.py.This is a sanity check, it may be too strict or too loose sometimes.
As runtime type checking with Python is complex, especially when dealing
with generics, if this fails and can't be fixed it's always possible
to skip the check. An alternative would be relying on some library like
although, at the time of writing, this library seems to be still in its early stages
More standard (generic) type checks are available only in Python 3.9+
def validate_type(name, t):
if t in supported_single_value_types:
return
if dataclasses.is_dataclass(t):
for f in dataclasses.fields(t):
validate_type(name + "." + f.name, f.type)
return
if getattr(t, "origin", None) is Union:
args = getattr(t, "args")
if len(args) == 2:
(tt, tt2) = args
if tt2 is not type(None): # noqa: E721
raise TypeError("Unions are not supported")
validate_type(name + "[T]", tt)
return
if getattr(t, "origin", None) is list:
(tt,) = getattr(t, "args")
validate_type(name + "[T]", tt)
return
if getattr(t, "origin", None) is dict:
(kt, vt) = getattr(t, "args")
if kt is not str:
raise TypeError(f"Field {name} has key type {t}, when only str is supported")
validate_type(name + "[_, V]", vt)
return
raise TypeError(f"Field {name} of unsupported type {t}")
if not dataclasses.is_dataclass(fields):
raise TypeError("Fields must be specified using a dataclass")
for field in dataclasses.fields(fields):
validate_type(field.name, field.type)
@staticmethod
def _validate_indices(
fields: type,
index_partition: Union[None, str, Callable[[object], Partition]],
index_id: Union[None, str, Callable[[object], Identifier]],
):
field_names = [f.name for f in dataclasses.fields(fields)]
if isinstance(index_partition, str) and index_partition not in field_names:
raise ValueError(
f"Cannot index by {index_partition} as {fields} doesn't have this field"
)
if isinstance(index_id, str) and index_id not in field_names:
raise ValueError(f"Cannot index by {index_id} as {fields} doesn't have this field")
@staticmethod
def _validate_value(field, value):
if not isinstance(value, field.type):
raise ValueError(
f"Value of field '{field.name}' of unsupported type {type(value)}")
pass
@staticmethod
def _validate_object(fields: type, x: object) -> object:
if not isinstance(x, fields):
raise TypeError(f"Expected {x} to be of type {fields}, instead of {type(x)}")
for field in dataclasses.fields(fields):
ContentAdapter._validate_value(field, getattr(x, field.name, None))
return x
@staticmethod
def _extract_partition(
index_partition: Union[str, Callable[[object], Partition]], x: object
) -> Partition:
assert index_partition
pid = (
getattr(x, index_partition) if isinstance(index_partition, str) else index_partition(x)
)
if not pid:
raise KeyError(f"Missing partition in object {x}")
if not isinstance(pid, str):
raise ValueError(f"Partition identifier {pid} is not a string")
isinstance can't be used if Partition is NewType
if not isinstance(pid, Partition):
raise TypeError(
f"{partition_field} of unsupported type {type(pid)} in object {x}"
)
return Partition(pid)
@staticmethod
def _extract_identifier(
index_id: Union[str, Callable[[object], Identifier]], x: object
) -> Identifier:
assert index_id
oid = getattr(x, index_id) if isinstance(index_id, str) else index_id(x)
if not oid:
raise KeyError(f"Missing identifier in object {x}")
if not isinstance(oid, str):
raise ValueError(f"Object identifier {oid} is not a string")
isinstance can't be used if Identifier is NewType
if not isinstance(oid, Identifier):
raise TypeError(
f"{identifier_field} of unsupported type {type(oid)} in object {x}"
)
return Identifier(oid)
@staticmethod
def _extract_refs(
index_ref: Union[str, Callable[[object], Union[Ref, Iterable[Ref]]]], x: object
) -> Iterable[Ref]:
assert index_ref
refs: Union[Ref, Iterable[Ref]] = (
getattr(x, index_ref) if isinstance(index_ref, str) else index_ref(x)
)
We support both single-reference and multi-references
return [refs] if isinstance(refs, Ref) else refs
[docs]
class Adapter(ABC):
"""
An adapter controls the encoding and decoding process of platform data.
It transforms data from and to adapter-specific data structure and supports
reading, writing, encoding and decoding a variety of MIME content types.
For the list of MIME content types supported when reading and writing
a layer with read_* and write_* functions of the :class:Layer
and its subclasses, please see documentation of specific adapters,
and the respective :class:Decoder and :class:Encoder class documentation.
All the operations involving content passes through an adapter when
the parameters encode or decode are True, their default value.
These are parameters of the read_* and write_* functions.
If a content type is not supported, or if reading or writing raw content is preferred,
pass False to skip encoding or decoding and deal with raw bytes instead.
If no adapter is specified, a default one is provided that is generic
enough to encode and decode a common content types from and to
standard Python classes, such as lists and dictionaries.
More sophisticated adapters, subclasses of Adapter, are provided in separate packages.
"""
@property
@abstractmethod
def encoder(self) -> Encoder:
"""The encoder associated with the adapter."""
@property
@abstractmethod
def decoder(self) -> Decoder:
"""The decoder associated with the adapter."""
@property
@abstractmethod
def content_adapter(self) -> ContentAdapter:
"""The adapter specialized for content."""
[docs]
@abstractmethod
def from_versioned_metadata(self, partitions: Iterator[VersionedPartition], **kwargs):
"""Adapt versioned partition metadata to the target format.
:param partitions: sequence of partition metadata from a versioned layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def from_versioned_data(
self,
partitions_data: Iterator[Tuple[VersionedPartition, bytes]],
content_type: str,
schema: Optional[Schema],
**kwargs,
):
"""Adapt versioned partition metadata and data to the target format.
:param partitions_data: sequence of partition metadata and data from a versioned layer
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_versioned_metadata(
self, layer: "VersionedLayer", partitions_update, partitions_delete, **kwargs
) -> Tuple[Iterator[VersionedPartition], Iterator[Union[str, int]]]:
"""Adapt what update and delete from the target format to versioned partition metadata.
:param layer: the layer all the metadata and data belong to
:param partitions_update: adapter-specific, the partitions metadata to update, if any
:param partitions_delete: adapter-specific, the partitions metadata to delete, if any
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: Iterator with the :class:VersionedPartition that are adapted
"""
[docs]
@abstractmethod
def to_versioned_data(
self,
layer: "VersionedLayer",
data,
content_type: str,
schema: Optional[Schema],
**kwargs,
) -> Iterator[Tuple[Union[str, int], bytes]]:
"""Adapt data from the target format to versioned partition metadata and data.
:param layer: the layer all the metadata and data belong to
:param data: adapter-specific, the data to adapt
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: sequence of partition id and data for the versioned layer # noqa
"""
[docs]
@abstractmethod
def from_volatile_metadata(self, partitions: Iterator[VolatilePartition], **kwargs):
"""Adapt volatile partition metadata to the target format.
:param partitions: sequence of partition metadata from a volatile layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def from_volatile_data(
self,
partitions_data: Iterator[Tuple[VolatilePartition, bytes]],
content_type: str,
schema: Optional[Schema],
**kwargs,
):
"""Adapt volatile partition metadata and data to the target format.
:param partitions_data: sequence of partition metadata and data from a volatile layer
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_volatile_metadata(
self, layer: "VolatileLayer", partitions_update, partitions_delete, **kwargs
) -> Tuple[Iterator[VolatilePartition], Iterator[Union[str, int]]]:
"""Adapt what update and delete from the target format to volatile partition metadata.
:param layer: the layer all the metadata and data belong to
:param partitions_update: adapter-specific, the partitions metadata to update, if any
:param partitions_delete: adapter-specific, the partitions metadata to delete, if any
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: tuple of Iterator, the first with the :class:VolatilePartition
that are adapted, the second with the partition ids to delete # noqa
"""
[docs]
@abstractmethod
def to_volatile_data(
self,
layer: "VolatileLayer",
data,
content_type: str,
schema: Optional[Schema],
**kwargs,
) -> Iterator[Tuple[Union[str, int], bytes]]:
"""Adapt data from the target format to volatile partition metadata and data.
:param layer: the layer all the metadata and data belong to
:param data: adapter-specific, the data to adapt
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: sequence of partition id and data for the volatile layer # noqa
"""
[docs]
@abstractmethod
def from_stream_metadata(self, partitions: Iterator[StreamPartition], **kwargs):
"""Adapt stream partition metadata to the target format.
:param partitions: sequence of partition metadata from a stream layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def from_stream_data(
self,
partitions_data: Iterator[Tuple[StreamPartition, bytes]],
content_type: str,
schema: Optional[Schema],
**kwargs,
):
"""Adapt stream partition metadata and data to the target format.
:param partitions_data: sequence of partition metadata and data from a stream layer
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_stream_metadata(
self, layer: "StreamLayer", partitions, **kwargs
) -> Iterator[StreamPartition]:
"""Adapt what to publish from the target format to stream partition metadata.
:param layer: the layer all the metadata and data belong to
:param partitions: adapter-specific, the partitions metadata to publish
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: Iterator with the :class:StreamPartition that are adapted
"""
[docs]
@abstractmethod
def to_stream_data(
self,
layer: "StreamLayer",
data,
content_type: str,
schema: Optional[Schema],
timestamp: Optional[int],
**kwargs,
) -> Iterator[Tuple[Union[str, int], bytes, Optional[int]]]:
"""Adapt data from the target format to stream partition metadata and data.
:param layer: the layer all the metadata and data belong to
:param data: adapter-specific, the data to adapt
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param timestamp: optional timestamp for all the messages, if none is specified in data:
in milliseconds since Unix epoch (1970-01-01T00:00:00 UTC)
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: sequence of partition id, data and timestamp for the stream layer # noqa
"""
[docs]
@abstractmethod
def from_index_metadata(self, partitions: Iterator[IndexPartition], **kwargs):
"""Adapt index partition metadata to the target format.
:param partitions: sequence of partition metadata from an index layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def from_index_data(
self,
partitions_data: Iterator[Tuple[IndexPartition, bytes]],
content_type: str,
schema: Optional[Schema],
**kwargs,
):
"""Adapt index partition metadata and data to the target format.
:param partitions_data: sequence of partition metadata and data from an index layer
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_index_single_data(
self,
data,
content_type: str,
schema: Optional[Schema],
**kwargs,
) -> bytes:
"""Adapt index data from the target format.
:param data: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports
:param content_type: the MIME content type of the layer
:param schema: optional :class:Schema of the layer
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: data encoded for an index layer # noqa
"""
[docs]
@abstractmethod
def from_feature_ids(self, feature_ids: Iterator[str], **kwargs):
"""Adapt a sequence of feature identifiers to the target format.
:param feature_ids: sequence of feature identifiers
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_feature_ids(self, data, **kwargs) -> Iterator[str]:
"""Adapt data from the target format to a sequence of feature identifiers.
:param data: adapter-specific, the data to adapt
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: sequence of feature identifiers # noqa
"""
[docs]
@abstractmethod
def from_geo_features(self, features: Iterator[geojson.Feature], **kwargs):
"""Adapt a sequence of geographic features to the target format.
:param features: sequence of geographic features
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: adapter-specific, please consult the documentation
of the specific adapter to for the formats and types it supports # noqa
"""
[docs]
@abstractmethod
def to_geo_features(self, data, **kwargs) -> Iterator[geojson.Feature]:
"""Adapt data from the target format to a sequence of geographic features.
:param data: adapter-specific, the data to adapt
:param kwargs: adapter-specific, please consult the documentation
of the specific adapter to for the parameters and types it supports
:return: sequence of geographic features # noqa
"""