Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more abstract geojson approach #173

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
from .datex2_mixin import Datex2Mixin
from .pull_converter import PullConverter
from .pull_scraper_mixin import PullScraperMixin
from .static_geojson_data_mixin import GeojsonFeatureGeometryInput, GeojsonInput, StaticGeojsonDataMixin
from .static_geojson_mixin import StaticGeojsonDataMixin

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@
from validataclass.exceptions import ValidationError
from validataclass.validators import DataclassValidator

from parkapi_sources.converters.base_converter.pull.static_geojson_data_mixin.models import (
GeojsonFeatureInput,
GeojsonInput,
)
from parkapi_sources.exceptions import ImportParkingSiteException, ImportSourceException
from parkapi_sources.models import SourceInfo, StaticParkingSiteInput
from parkapi_sources.models import GeojsonFeatureInput, GeojsonInput, SourceInfo, StaticParkingSiteInput
from parkapi_sources.util import ConfigHelper


Expand Down Expand Up @@ -57,6 +53,25 @@ def _get_static_parking_site_inputs_and_exceptions(
self,
source_uid: str,
) -> tuple[list[StaticParkingSiteInput], list[ImportParkingSiteException]]:
static_parking_site_inputs: list[StaticParkingSiteInput] = []

feature_inputs, import_parking_site_exceptions = self._get_features_and_exceptions(source_uid)

for feature_input in feature_inputs:
static_parking_site_inputs.append(
feature_input.to_static_parking_site_input(
# TODO: Use the Last-Updated HTTP header instead, but as Github does not set such an header, we
# need to move all GeoJSON data in order to use this.
static_data_updated_at=datetime.now(tz=timezone.utc),
),
)

return static_parking_site_inputs, import_parking_site_exceptions

def _get_features_and_exceptions(
the-infinity marked this conversation as resolved.
Show resolved Hide resolved
self,
source_uid: str,
) -> tuple[list[GeojsonFeatureInput], list[ImportParkingSiteException]]:
geojson_dict = self._get_static_geojson(source_uid)
try:
geojson_input = self.geojson_validator.validate(geojson_dict)
Expand All @@ -66,19 +81,12 @@ def _get_static_parking_site_inputs_and_exceptions(
message=f'Invalid GeoJSON for source {source_uid}: {e.to_dict()}. Data: {geojson_dict}',
) from e

static_parking_site_inputs: list[StaticParkingSiteInput] = []
feature_inputs: list[GeojsonFeatureInput] = []
import_parking_site_exceptions: list[ImportParkingSiteException] = []

for feature_dict in geojson_input.features:
try:
feature_input: GeojsonFeatureInput = self.geojson_feature_validator.validate(feature_dict)
static_parking_site_inputs.append(
feature_input.to_static_parking_site_input(
# TODO: Use the Last-Updated HTTP header instead, but as Github does not set such an header, we need to move
# all GeoJSON data in order to use this.
static_data_updated_at=datetime.now(tz=timezone.utc),
),
)
feature_inputs.append(self.geojson_feature_validator.validate(feature_dict))
except ValidationError as e:
import_parking_site_exceptions.append(
ImportParkingSiteException(
Expand All @@ -87,4 +95,5 @@ def _get_static_parking_site_inputs_and_exceptions(
message=f'Invalid GeoJSON feature for source {source_uid}: {e.to_dict()}',
),
)
return static_parking_site_inputs, import_parking_site_exceptions

return feature_inputs, import_parking_site_exceptions
4 changes: 2 additions & 2 deletions src/parkapi_sources/converters/freiburg/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from validataclass.exceptions import ValidationError
from validataclass.validators import DataclassValidator

from parkapi_sources.converters.base_converter.pull import GeojsonInput, PullConverter, StaticGeojsonDataMixin
from parkapi_sources.converters.base_converter.pull import PullConverter, StaticGeojsonDataMixin
from parkapi_sources.exceptions import ImportParkingSiteException, ImportSourceException
from parkapi_sources.models import RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput
from parkapi_sources.models import GeojsonInput, RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput

from .models import FreiburgFeatureInput

Expand Down
4 changes: 2 additions & 2 deletions src/parkapi_sources/converters/herrenberg_bike/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from validataclass.exceptions import ValidationError
from validataclass.validators import DataclassValidator

from parkapi_sources.converters.base_converter.pull import GeojsonInput, PullConverter
from parkapi_sources.converters.base_converter.pull import PullConverter
from parkapi_sources.exceptions import ImportParkingSiteException, ImportSourceException
from parkapi_sources.models import RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput
from parkapi_sources.models import GeojsonInput, RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput

from .models import HerrenbergBikeFeatureInput

Expand Down
63 changes: 30 additions & 33 deletions src/parkapi_sources/converters/herrenberg_bike/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Optional

from validataclass.dataclasses import DefaultUnset, ValidataclassMixin, validataclass
from validataclass.helpers import OptionalUnset, UnsetValue
from validataclass.helpers import OptionalUnset
from validataclass.validators import (
DataclassValidator,
EnumValidator,
Expand All @@ -19,8 +19,7 @@
UrlValidator,
)

from parkapi_sources.converters.base_converter.pull import GeojsonFeatureGeometryInput
from parkapi_sources.models import StaticParkingSiteInput
from parkapi_sources.models import GeojsonBaseFeatureInput, StaticParkingSiteInput
from parkapi_sources.models.enums import ParkingSiteType, PurposeType, SupervisionType
from parkapi_sources.validators import MappedBooleanValidator, ParsedDateValidator

Expand Down Expand Up @@ -112,7 +111,8 @@ class HerrenbergBikePropertiesInput(ValidataclassMixin):
operator_name: OptionalUnset[str] = NoneToUnsetValue(StringValidator(min_length=0, max_length=256)), DefaultUnset
capacity: int = IntegerValidator(min_value=0)
capacity_charging: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
capacity_cargobike: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
# capacity_cargobike is unsupported yet
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is it known when it will be supported? I am wondering if it would help to write the comment as a TODO + number of the issue where it will be fixed, so it's easier to find it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not do a TODO, as it is no TODO atm, but linked the ticket: #174

# capacity_cargobike: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
max_height: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
max_width: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
supervision_type: OptionalUnset[HerrenbergBikeSupervisionType] = (
Expand All @@ -123,10 +123,11 @@ class HerrenbergBikePropertiesInput(ValidataclassMixin):
NoneToUnsetValue(MappedBooleanValidator(mapping={'true': True, 'false': False})),
DefaultUnset,
)
access: OptionalUnset[HerrenbergBikeAccessType] = (
NoneToUnsetValue(EnumValidator(HerrenbergBikeAccessType)),
DefaultUnset,
)
# access is unsupported yet
# access: OptionalUnset[HerrenbergBikeAccessType] = (
# NoneToUnsetValue(EnumValidator(HerrenbergBikeAccessType)),
# DefaultUnset,
# )
date_surveyed: OptionalUnset[date] = NoneToUnsetValue(ParsedDateValidator(date_format='%Y-%m-%d')), DefaultUnset
has_lighting: OptionalUnset[bool] = (
NoneToUnsetValue(MappedBooleanValidator(mapping={'true': True, 'false': False})),
Expand All @@ -145,36 +146,32 @@ class HerrenbergBikePropertiesInput(ValidataclassMixin):
max_stay: OptionalUnset[int] = NoneToUnsetValue(IntegerValidator(min_value=0)), DefaultUnset
fee_description: OptionalUnset[str] = NoneToUnsetValue(StringValidator(max_length=512)), DefaultUnset

def to_dict(self, *args, **kwargs) -> dict:
ignore_keys = ['type', 'original_uid', 'supervision_type', 'date_surveyed']
result = {key: value for key, value in super().to_dict(*args, **kwargs).items() if key not in ignore_keys}
the-infinity marked this conversation as resolved.
Show resolved Hide resolved

result['uid'] = self.original_uid
result['purpose'] = PurposeType.BIKE
result['type'] = self.type.to_parking_site_type()
if result['name'] == '':
result['name'] = 'Fahrrad-Abstellanlagen'
if self.date_surveyed:
result['static_data_updated_at'] = datetime.combine(self.date_surveyed, time(), tzinfo=timezone.utc)
else:
result['static_data_updated_at'] = datetime.now(timezone.utc)
if self.supervision_type:
result['supervision_type'] = self.supervision_type.to_supervision_type()

return result


@validataclass
class HerrenbergBikeFeatureInput:
geometry: GeojsonFeatureGeometryInput = DataclassValidator(GeojsonFeatureGeometryInput)
class HerrenbergBikeFeatureInput(GeojsonBaseFeatureInput):
properties: HerrenbergBikePropertiesInput = DataclassValidator(HerrenbergBikePropertiesInput)

def to_static_parking_site_input(self) -> StaticParkingSiteInput:
def to_static_parking_site_input(self, **kwargs) -> StaticParkingSiteInput:
return StaticParkingSiteInput(
uid=str(self.properties.original_uid),
name=self.properties.name if self.properties.name != '' else 'Fahrrad-Abstellanlagen',
type=self.properties.type.to_parking_site_type(),
description=self.properties.description,
capacity=self.properties.capacity,
has_realtime_data=self.properties.has_realtime_data,
has_lighting=self.properties.has_lighting,
is_covered=self.properties.is_covered,
related_location=self.properties.related_location,
operator_name=self.properties.operator_name,
max_height=self.properties.max_height,
max_width=self.properties.max_width,
has_fee=self.properties.has_fee,
supervision_type=self.properties.supervision_type.to_supervision_type()
if self.properties.supervision_type is not UnsetValue
else UnsetValue,
fee_description=self.properties.fee_description,
capacity_charging=self.properties.capacity_charging,
lat=self.geometry.coordinates[1],
lon=self.geometry.coordinates[0],
static_data_updated_at=datetime.now(timezone.utc)
if self.properties.date_surveyed is UnsetValue
else datetime.combine(self.properties.date_surveyed, time(), tzinfo=timezone.utc),
purpose=PurposeType.BIKE,
**self.properties.to_dict(**kwargs),
)
4 changes: 2 additions & 2 deletions src/parkapi_sources/converters/karlsruhe/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from validataclass.exceptions import ValidationError
from validataclass.validators import DataclassValidator

from parkapi_sources.converters.base_converter.pull import GeojsonInput, PullConverter
from parkapi_sources.converters.base_converter.pull import PullConverter
from parkapi_sources.exceptions import ImportParkingSiteException, ImportSourceException
from parkapi_sources.models import RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput
from parkapi_sources.models import GeojsonInput, RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput

from .models import KarlsruheBikeFeatureInput, KarlsruheFeatureInput

Expand Down
39 changes: 23 additions & 16 deletions src/parkapi_sources/converters/karlsruhe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@
UrlValidator,
)

from parkapi_sources.converters.base_converter.pull import GeojsonFeatureGeometryInput
from parkapi_sources.models import RealtimeParkingSiteInput, StaticParkingSiteInput
from parkapi_sources.models import (
GeojsonBaseFeatureInput,
GeojsonBaseFeaturePropertiesInput,
RealtimeParkingSiteInput,
StaticParkingSiteInput,
)
from parkapi_sources.models.enums import OpeningStatus, ParkingSiteType, PurposeType
from parkapi_sources.validators import MappedBooleanValidator, ParsedDateValidator

Expand All @@ -41,7 +45,7 @@ def to_opening_status(self) -> OpeningStatus | None:


@validataclass
class KarlsruhePropertiesInput:
class KarlsruhePropertiesInput(GeojsonBaseFeaturePropertiesInput):
id: int = IntegerValidator()
parkhaus_name: str = StringValidator()
gesamte_parkplaetze: int = IntegerValidator(min_value=0)
Expand All @@ -68,22 +72,25 @@ def __post_init__(self):


@validataclass
class KarlsruheFeatureInput:
class KarlsruheFeatureInput(GeojsonBaseFeatureInput):
id: str = StringValidator()
geometry: GeojsonFeatureGeometryInput = DataclassValidator(GeojsonFeatureGeometryInput)
properties: KarlsruhePropertiesInput = DataclassValidator(KarlsruhePropertiesInput)

def to_static_parking_site_input(self) -> StaticParkingSiteInput:
address = (
f'{self.properties.parkhaus_strasse}, {self.properties.parkhaus_plz} {self.properties.parkhaus_gemeinde}'
)
max_height = (
None if self.properties.max_durchfahrtshoehe is None else int(self.properties.max_durchfahrtshoehe * 100)
)
return StaticParkingSiteInput(
uid=str(self.properties.id),
name=self.properties.parkhaus_name,
lat=self.geometry.coordinates[1],
lon=self.geometry.coordinates[0],
name=self.properties.parkhaus_name,
capacity=self.properties.gesamte_parkplaetze,
address=f'{self.properties.parkhaus_strasse}, {self.properties.parkhaus_plz} {self.properties.parkhaus_gemeinde}',
max_height=None
if self.properties.max_durchfahrtshoehe is None
else int(self.properties.max_durchfahrtshoehe * 100),
address=address,
max_height=max_height,
public_url=self.properties.parkhaus_internet,
static_data_updated_at=datetime.combine(self.properties.stand_stammdaten, time(), tzinfo=timezone.utc),
has_realtime_data=self.properties.echtzeit_belegung,
Expand Down Expand Up @@ -125,7 +132,7 @@ def to_parking_site_type(self) -> ParkingSiteType:


@validataclass
class KarlsruheBikePropertiesInput:
class KarlsruheBikePropertiesInput(GeojsonBaseFeaturePropertiesInput):
id: int = IntegerValidator()
art: OptionalUnset[KarlsruheBikeType] = NoneToUnsetValue(EnumValidator(KarlsruheBikeType)), DefaultUnset
standort: str = StringValidator()
Expand All @@ -137,13 +144,15 @@ class KarlsruheBikePropertiesInput:


@validataclass
class KarlsruheBikeFeatureInput:
geometry: GeojsonFeatureGeometryInput = DataclassValidator(GeojsonFeatureGeometryInput)
class KarlsruheBikeFeatureInput(GeojsonBaseFeatureInput):
properties: KarlsruheBikePropertiesInput = DataclassValidator(KarlsruheBikePropertiesInput)

def to_static_parking_site_input(self) -> StaticParkingSiteInput:
address_fragments = [self.properties.standort, self.properties.stadtteil, self.properties.gemeinde]
address = ', '.join([fragment for fragment in address_fragments if fragment is not UnsetValue])
parking_site_type = (
ParkingSiteType.OTHER if self.properties.art is UnsetValue else self.properties.art.to_parking_site_type()
)
return StaticParkingSiteInput(
uid=str(self.properties.id),
name=self.properties.standort,
Expand All @@ -155,8 +164,6 @@ def to_static_parking_site_input(self) -> StaticParkingSiteInput:
is_covered=self.properties.art == KarlsruheBikeType.STANDS_WITH_ROOF or UnsetValue,
description=self.properties.bemerkung,
static_data_updated_at=datetime.now(timezone.utc),
type=ParkingSiteType.OTHER
if self.properties.art is UnsetValue
else self.properties.art.to_parking_site_type(),
type=parking_site_type,
purpose=PurposeType.BIKE,
)
Loading