-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c4fa620
commit a238cc3
Showing
7 changed files
with
178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
""" | ||
Copyright 2024 binary butterfly GmbH | ||
Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. | ||
""" | ||
|
||
from .converter import BaselPullConverter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
""" | ||
Copyright 2024 binary butterfly GmbH | ||
Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. | ||
""" | ||
|
||
from datetime import datetime | ||
|
||
import requests | ||
from validataclass.exceptions import ValidationError | ||
from validataclass.validators import DataclassValidator, ListValidator, AnythingValidator | ||
|
||
from parkapi_sources.converters.base_converter.pull import PullConverter | ||
from parkapi_sources.converters.basel.models import BaselParkingSiteInput | ||
from parkapi_sources.exceptions import ImportParkingSiteException | ||
from parkapi_sources.models import RealtimeParkingSiteInput, SourceInfo, StaticParkingSiteInput | ||
|
||
|
||
class BaselPullConverter(PullConverter): | ||
parking_sites_input_validator = ListValidator(AnythingValidator(allowed_types=[dict])) | ||
parking_site_validator = DataclassValidator(BaselParkingSiteInput) | ||
|
||
source_info = SourceInfo( | ||
uid='basel', | ||
name='Stadt Basel', | ||
public_url='https://www.parkleitsystem-basel.ch', | ||
source_url='https://data.bs.ch/api/v2/catalog/datasets/100088/exports/json', | ||
timezone='Europe/Berlin', | ||
attribution_contributor='Stadt Basel', | ||
has_realtime_data=True, | ||
) | ||
|
||
def get_static_parking_sites(self) -> tuple[list[StaticParkingSiteInput], list[ImportParkingSiteException]]: | ||
static_parking_site_inputs: list[StaticParkingSiteInput] = [] | ||
parking_site_inputs, parking_site_errors = self._get_parking_site_inputs() | ||
|
||
for parking_site_input in parking_site_inputs: | ||
static_parking_site_inputs.append(parking_site_input.to_static_parking_site()) | ||
|
||
return static_parking_site_inputs, parking_site_errors | ||
|
||
def get_realtime_parking_sites(self) -> tuple[list[RealtimeParkingSiteInput], list[ImportParkingSiteException]]: | ||
realtime_parking_site_inputs: list[RealtimeParkingSiteInput] = [] | ||
parking_site_inputs, parking_site_errors = self._get_parking_site_inputs() | ||
|
||
for parking_site_input in parking_site_inputs: | ||
realtime_parking_site_inputs.append(parking_site_input.to_realtime_parking_site()) | ||
|
||
return realtime_parking_site_inputs, parking_site_errors | ||
|
||
def _get_parking_site_inputs(self) -> tuple[list[BaselParkingSiteInput], list[ImportParkingSiteException]]: | ||
parking_site_inputs: list[BaselParkingSiteInput] = [] | ||
parking_site_errors: list[ImportParkingSiteException] = [] | ||
|
||
response = requests.get(self.source_info.source_url, timeout=60) | ||
parking_sites_dicts: list[dict] = self.parking_sites_input_validator.validate(response.json()) | ||
|
||
for parking_site_dict in parking_sites_dicts: | ||
try: | ||
parking_site_inputs.append(self.parking_site_validator.validate(parking_site_dict)) | ||
except ValidationError as e: | ||
parking_site_errors.append( | ||
ImportParkingSiteException( | ||
source_uid=self.source_info.uid, | ||
parking_site_uid=parking_site_dict.get('id'), | ||
message=f'validation error for static data {parking_site_dict}: {e.to_dict()}', | ||
), | ||
) | ||
|
||
return parking_site_inputs, parking_site_errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
""" | ||
Copyright 2024 binary butterfly GmbH | ||
Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. | ||
""" | ||
|
||
from datetime import datetime, timezone | ||
from decimal import Decimal | ||
|
||
from validataclass.dataclasses import validataclass | ||
from validataclass.validators import StringValidator, IntegerValidator, NumericValidator, UrlValidator, DateTimeValidator, \ | ||
DataclassValidator | ||
|
||
from parkapi_sources.models import StaticParkingSiteInput, RealtimeParkingSiteInput | ||
|
||
|
||
@validataclass | ||
class BaselCoordinates: | ||
lat: Decimal = NumericValidator() | ||
lon: Decimal = NumericValidator() | ||
|
||
|
||
@validataclass | ||
class BaselParkingSiteInput: | ||
title: str = StringValidator() | ||
id2: str = StringValidator() | ||
name: str = StringValidator() | ||
total: int = IntegerValidator(min_value=0) | ||
free: int = IntegerValidator(min_value=0) | ||
link: str = UrlValidator() | ||
geo_point_2d: BaselCoordinates = DataclassValidator(BaselCoordinates) | ||
published: datetime = DateTimeValidator( | ||
local_timezone=timezone.utc, | ||
target_timezone=timezone.utc, | ||
discard_milliseconds=True, | ||
) | ||
|
||
def to_static_parking_site(self) -> StaticParkingSiteInput: | ||
return StaticParkingSiteInput( | ||
uid=self.id2, | ||
name=self.name, | ||
lat=self.geo_point_2d.lat, | ||
lon=self.geo_point_2d.lon, | ||
capacity=self.total, | ||
public_url=self.link, | ||
static_data_updated_at=self.published, | ||
) | ||
|
||
def to_realtime_parking_site(self) -> RealtimeParkingSiteInput: | ||
return RealtimeParkingSiteInput( | ||
uid=self.id2, | ||
realtime_capacity=self.total, | ||
realtime_free_capacity=self.free, | ||
realtime_data_updated_at=self.published, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
""" | ||
Copyright 2024 binary butterfly GmbH | ||
Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt. | ||
""" | ||
|
||
from pathlib import Path | ||
from unittest.mock import Mock | ||
|
||
import pytest | ||
from parkapi_sources.converters.basel import BaselPullConverter | ||
from requests_mock import Mocker | ||
|
||
from tests.converters.helper import validate_realtime_parking_site_inputs, validate_static_parking_site_inputs | ||
|
||
|
||
@pytest.fixture | ||
def basel_pull_converter(mocked_config_helper: Mock, requests_mock: Mocker) -> BaselPullConverter: | ||
json_path = Path(Path(__file__).parent, 'data', 'basel.json') | ||
with json_path.open() as json_file: | ||
json_data = json_file.read() | ||
|
||
requests_mock.get('https://data.bs.ch/api/v2/catalog/datasets/100088/exports/json', text=json_data) | ||
|
||
return BaselPullConverter(config_helper=mocked_config_helper) | ||
|
||
|
||
class BaselPullConverterTest: | ||
@staticmethod | ||
def test_get_static_parking_sites(basel_pull_converter: BaselPullConverter): | ||
static_parking_site_inputs, import_parking_site_exceptions = basel_pull_converter.get_static_parking_sites() | ||
print(import_parking_site_exceptions) | ||
|
||
assert len(static_parking_site_inputs) == 16 | ||
assert len(import_parking_site_exceptions) == 1 | ||
|
||
validate_static_parking_site_inputs(static_parking_site_inputs) | ||
|
||
@staticmethod | ||
def test_get_realtime_parking_sites(basel_pull_converter: BaselPullConverter): | ||
realtime_parking_site_inputs, import_parking_site_exceptions = basel_pull_converter.get_realtime_parking_sites() | ||
|
||
assert len(realtime_parking_site_inputs) == 16 | ||
assert len(import_parking_site_exceptions) == 1 | ||
|
||
validate_realtime_parking_site_inputs(realtime_parking_site_inputs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
[{"title": "Parkhaus Bad. Bahnhof", "published": "2024-07-28T12:06:00+00:00", "free": 286, "total": 750, "anteil_frei": 0.38133333333333336, "auslastung": 0.6186666666666667, "auslastung_prozent": 61.86666666666667, "link": "https://www.parkleitsystem-basel.ch/parkhaus/badbahnhof", "geo_point_2d": {"lon": 7.6089067, "lat": 47.5651794}, "description": "Anzahl freie Parkpl\u00e4tze: 286", "name": "Bad. Bahnhof", "id2": "badbahnhof"},{"title": "Parkhaus Claramatte", "published": "2024-07-28T12:06:00+00:00", "free": 113, "total": 170, "anteil_frei": 0.6647058823529411, "auslastung": 0.33529411764705885, "auslastung_prozent": 33.529411764705884, "link": "https://www.parkleitsystem-basel.ch/parkhaus/claramatte", "geo_point_2d": {"lon": 7.5946604, "lat": 47.5639644}, "description": "Anzahl freie Parkpl\u00e4tze: 113", "name": "Claramatte", "id2": "claramatte"},{"title": "Parkhaus Steinen", "published": "2024-07-28T12:06:00+00:00", "free": 244, "total": 526, "anteil_frei": 0.46387832699619774, "auslastung": 0.5361216730038023, "auslastung_prozent": 53.61216730038023, "link": "https://www.parkleitsystem-basel.ch/parkhaus/steinen", "geo_point_2d": {"lon": 7.5858936, "lat": 47.5524554}, "description": "Anzahl freie Parkpl\u00e4tze: 244", "name": "Steinen", "id2": "steinen"},{"title": "Parkhaus City", "published": "2024-07-28T12:06:00+00:00", "free": 805, "total": 1114, "anteil_frei": 0.72262118491921, "auslastung": 0.27737881508079, "auslastung_prozent": 27.737881508079, "link": "https://www.parkleitsystem-basel.ch/parkhaus/city", "geo_point_2d": {"lon": 7.5824076, "lat": 47.561101}, "description": "Anzahl freie Parkpl\u00e4tze: 805", "name": "City", "id2": "city"},{"title": "Parkhaus Storchen", "published": "2024-07-28T12:06:00+00:00", "free": 38, "total": 142, "anteil_frei": 0.2676056338028169, "auslastung": 0.7323943661971831, "auslastung_prozent": 73.23943661971832, "link": "https://www.parkleitsystem-basel.ch/parkhaus/storchen", "geo_point_2d": {"lon": 7.58658, "lat": 47.5592347}, "description": "Anzahl freie Parkpl\u00e4tze: 38", "name": "Storchen", "id2": "storchen"},{"title": "Parkhaus Aeschen", "published": "2024-07-28T12:06:00+00:00", "free": 83, "total": 97, "anteil_frei": 0.8556701030927835, "auslastung": 0.14432989690721654, "auslastung_prozent": 14.432989690721653, "link": "https://www.parkleitsystem-basel.ch/parkhaus/aeschen", "geo_point_2d": {"lon": 7.5943046, "lat": 47.5504299}, "description": "Anzahl freie Parkpl\u00e4tze: 83", "name": "Aeschen", "id2": "aeschen"},{"title": "Parkhaus Kunstmuseum", "published": "2024-07-28T12:06:00+00:00", "free": 251, "total": 350, "anteil_frei": 0.7171428571428572, "auslastung": 0.2828571428571428, "auslastung_prozent": 28.28571428571428, "link": "https://www.parkleitsystem-basel.ch/parkhaus/kunstmuseum", "geo_point_2d": {"lon": 7.5927014, "lat": 47.5545146}, "description": "Anzahl freie Parkpl\u00e4tze: 251", "name": "Kunstmuseum", "id2": "kunstmuseum"},{"title": "Zur Zeit haben wir keine aktuellen Parkhausdaten erhalten", "published": null, "free": null, "total": null, "anteil_frei": null, "auslastung": null, "auslastung_prozent": null, "link": null, "geo_point_2d": null, "description": null, "name": null, "id2": null},{"title": "Parkhaus Messe", "published": "2024-07-28T12:06:00+00:00", "free": 711, "total": 752, "anteil_frei": 0.9454787234042553, "auslastung": 0.05452127659574468, "auslastung_prozent": 5.452127659574469, "link": "https://www.parkleitsystem-basel.ch/parkhaus/messe", "geo_point_2d": {"lon": 7.602175, "lat": 47.563241}, "description": "Anzahl freie Parkpl\u00e4tze: 711", "name": "Messe", "id2": "messe"},{"title": "Parkhaus Europe", "published": "2024-07-28T12:06:00+00:00", "free": 87, "total": 120, "anteil_frei": 0.725, "auslastung": 0.275, "auslastung_prozent": 27.500000000000004, "link": "https://www.parkleitsystem-basel.ch/parkhaus/europe", "geo_point_2d": {"lon": 7.5967098, "lat": 47.5630411}, "description": "Anzahl freie Parkpl\u00e4tze: 87", "name": "Europe", "id2": "europe"},{"title": "Parkhaus Rebgasse", "published": "2024-07-28T12:06:00+00:00", "free": 242, "total": 250, "anteil_frei": 0.968, "auslastung": 0.03200000000000003, "auslastung_prozent": 3.200000000000003, "link": "https://www.parkleitsystem-basel.ch/parkhaus/rebgasse", "geo_point_2d": {"lon": 7.594263, "lat": 47.5607142}, "description": "Anzahl freie Parkpl\u00e4tze: 242", "name": "Rebgasse", "id2": "rebgasse"},{"title": "Parkhaus Clarahuus", "published": "2024-07-28T12:06:00+00:00", "free": 22, "total": 52, "anteil_frei": 0.4230769230769231, "auslastung": 0.5769230769230769, "auslastung_prozent": 57.692307692307686, "link": "https://www.parkleitsystem-basel.ch/parkhaus/clarahuus", "geo_point_2d": {"lon": 7.5917937, "lat": 47.5622725}, "description": "Anzahl freie Parkpl\u00e4tze: 22", "name": "Clarahuus", "id2": "clarahuus"},{"title": "Parkhaus Elisabethen", "published": "2024-07-28T12:06:00+00:00", "free": 542, "total": 840, "anteil_frei": 0.6452380952380953, "auslastung": 0.3547619047619047, "auslastung_prozent": 35.476190476190474, "link": "https://www.parkleitsystem-basel.ch/parkhaus/elisabethen", "geo_point_2d": {"lon": 7.5874932, "lat": 47.5506254}, "description": "Anzahl freie Parkpl\u00e4tze: 542", "name": "Elisabethen", "id2": "elisabethen"},{"title": "Parkhaus Post Basel", "published": "2024-07-28T12:06:00+00:00", "free": 71, "total": 72, "anteil_frei": 0.9861111111111112, "auslastung": 0.01388888888888884, "auslastung_prozent": 1.388888888888884, "link": "https://www.parkleitsystem-basel.ch/parkhaus/postbasel", "geo_point_2d": {"lon": 7.5929374, "lat": 47.5468617}, "description": "Anzahl freie Parkpl\u00e4tze: 71", "name": "Post Basel", "id2": "postbasel"},{"title": "Parkhaus Bahnhof S\u00fcd", "published": "2024-07-28T12:06:00+00:00", "free": 50, "total": 100, "anteil_frei": 0.5, "auslastung": 0.5, "auslastung_prozent": 50.0, "link": "https://www.parkleitsystem-basel.ch/parkhaus/bahnhofsued", "geo_point_2d": {"lon": 7.5884556, "lat": 47.5458851}, "description": "Anzahl freie Parkpl\u00e4tze: 50", "name": "Bahnhof S\u00fcd", "id2": "bahnhofsued"},{"title": "Parkhaus Anfos", "published": "2024-07-28T12:06:00+00:00", "free": 167, "total": 162, "anteil_frei": 1.0308641975308641, "auslastung": -0.030864197530864113, "auslastung_prozent": -3.0864197530864113, "link": "https://www.parkleitsystem-basel.ch/parkhaus/anfos", "geo_point_2d": {"lon": 7.593512, "lat": 47.5515968}, "description": "Anzahl freie Parkpl\u00e4tze: 167", "name": "Anfos", "id2": "anfos"},{"title": "Parkhaus Centralbahnparking", "published": "2024-07-28T12:06:00+00:00", "free": 123, "total": 286, "anteil_frei": 0.43006993006993005, "auslastung": 0.56993006993007, "auslastung_prozent": 56.993006993007, "link": "https://www.parkleitsystem-basel.ch/parkhaus/centralbahnparking", "geo_point_2d": {"lon": 7.5922975, "lat": 47.547299}, "description": "Anzahl freie Parkpl\u00e4tze: 123", "name": "Centralbahnparking", "id2": "centralbahnparking"}] |