Skip to main content

Async integration layer between Home Assistant and Dutch municipal visitor parking APIs.

Project description

pyCityVisitorParking

PyPI version Python versions CI

Async Python library for Dutch municipal visitor parking providers.

The library exposes a small provider-agnostic API for:

  • provider discovery
  • permit lookup
  • reservation management
  • favorites management

It is designed for Home Assistant use, but can also be used directly in any async Python application.

Status

Current bundled providers:

  • DVS Portal
  • The Hague
  • 2park

Provider manifests are discovered from src/pycityvisitorparking/provider/ without importing all providers up front.

Provider documentation:

Supported municipalities

For the exact base_url, api_uri, and provider-specific notes, see the provider README files.

  • DVS Portal: Apeldoorn, Bloemendaal, Delft, Den Bosch, Doetinchem (via Buha), Groningen, Haarlem, Harlingen, Heemstede, Heerenveen, Heerlen, Hengelo, Katwijk, Leiden, Leidschendam-Voorburg, Middelburg, Nissewaard, Oldenzaal, Rijswijk, Roermond, Schouwen-Duiveland, Sittard-Geleen, Smallingerland, Sudwest-Fryslan, Veere, Venlo, Vlissingen, Waadhoeke, Waalwijk, Weert, Zaanstad, Zevenaar, Zutphen, Zwolle
  • The Hague: The Hague
  • 2park: Amstelveen, Assen, Bergen op Zoom, Breda, Deventer, Dordrecht, Eindhoven, Emmen, Etten-Leur, Gorinchem, Hardenberg, Harderwijk, Maastricht, Oosterhout, Oss, Roosendaal, Sluis, Terneuzen, Tiel, Veenendaal, Vlaardingen

The 2park list is non-exhaustive. Check https://mijn.2park.nl for the current provider coverage.

Installation

Requires Python 3.14 or newer.

pip install pycityvisitorparking

Quickstart

import asyncio

from pycityvisitorparking import Client


async def main() -> None:
    async with Client(base_url="https://example", api_uri="/api") as client:
        provider = await client.get_provider("dvsportal")
        await provider.login(credentials={"username": "user", "password": "secret"})

        permit = await provider.get_permit()
        reservations = await provider.list_reservations()

        print(permit)
        print(reservations)


asyncio.run(main())

Public API

Client

Client is the main entry point.

  • list_providers() returns available ProviderInfo objects.
  • get_provider(provider_id, ...) loads a specific provider on demand.
  • Client accepts an optional injected aiohttp.ClientSession.
  • If you do not inject a session, the client creates and owns its own session.

Configuration options:

  • base_url: provider base URL
  • api_uri: optional provider API path
  • timeout: optional aiohttp.ClientTimeout
  • retry_count: retry count for idempotent GET requests

Data models

The public data models are:

  • ProviderInfo
  • Permit
  • ZoneValidityBlock
  • Reservation
  • Favorite

Model highlights:

  • ProviderInfo includes id, favorite_update_fields, reservation_update_fields, and balance_units
  • Permit includes id, remaining_balance, balance_unit, and zone_validity
  • ZoneValidityBlock contains UTC ISO 8601 start_time and end_time
  • Reservation includes id, name, license_plate, start_time, and end_time
  • Favorite includes id, name, and license_plate

Standardized behavior

  • Timestamps are returned as UTC ISO 8601 strings.
  • License plates are normalized and validated.
  • The public API stays provider-agnostic.
  • Some update operations may be unsupported by a provider; inspect favorite_update_fields and reservation_update_fields.

Common usage

List providers:

import asyncio

from pycityvisitorparking import Client


async def main() -> None:
    async with Client() as client:
        for provider in await client.list_providers():
            print(provider.id, provider.reservation_update_fields)


asyncio.run(main())

Manage a reservation:

import asyncio
from datetime import datetime, timedelta, timezone

from pycityvisitorparking import Client


async def main() -> None:
    async with Client(base_url="https://example", api_uri="/api") as client:
        provider = await client.get_provider("dvsportal")
        await provider.login(credentials={"username": "user", "password": "secret"})

        start_time = datetime(2024, 5, 1, 9, 0, tzinfo=timezone.utc)
        end_time = start_time + timedelta(hours=2)

        reservation = await provider.start_reservation(
            "12AB34",
            start_time=start_time,
            end_time=end_time,
            name="Visitor",
        )

        print(reservation.id)


asyncio.run(main())

Manage favorites:

import asyncio

from pycityvisitorparking import Client
from pycityvisitorparking.exceptions import ProviderError


async def main() -> None:
    async with Client(base_url="https://example", api_uri="/api") as client:
        provider = await client.get_provider("dvsportal")
        await provider.login(credentials={"username": "user", "password": "secret"})

        favorite = await provider.add_favorite("12AB34", name="Visitor")

        try:
            updated = await provider.update_favorite(favorite.id, name="Visitor 2")
            print(updated)
        except ProviderError:
            print("Favorite updates are not supported by this provider")


asyncio.run(main())

Error handling

Public methods raise library exceptions instead of raw aiohttp exceptions:

  • AuthError
  • NetworkError
  • ValidationError
  • ProviderError
  • RateLimitError
  • ServiceUnavailableError
  • NotFoundError
  • TimeoutError
  • ConfigError

Each exception includes normalized metadata such as error_code, detail, and optional user_message.

Example:

from pycityvisitorparking import Client
from pycityvisitorparking.exceptions import AuthError, NetworkError, ProviderError, ValidationError

async with Client(base_url=base_url, api_uri=api_uri) as client:
    try:
        provider = await client.get_provider("dvsportal")
        await provider.login(credentials={"username": "user", "password": "secret"})
        permit = await provider.get_permit()
    except (AuthError, ValidationError) as exc:
        handle_auth_or_input_error(exc)
    except NetworkError as exc:
        handle_network_issue(exc)
    except ProviderError as exc:
        handle_provider_issue(exc)

Logging

The library logs to the pycityvisitorparking logger using the standard Python logging module.

  • credentials are not logged
  • full license plates are not logged
  • request context can be attached for clearer diagnostics

Development

This repository uses uv for local development tasks.

Common checks:

uv run --group lint ruff check .
uv run --group lint ruff format --check .
uv run --group typecheck pyright
uv run --group test pytest
uv run --group schema python -m pytest -o addopts=-q tests/test_manifest_schema.py
uv build
uvx twine check dist/*

Release notes and publishing are handled through GitHub Actions. See docs/RELEASING.md.

Provider development

Providers live under src/pycityvisitorparking/provider/<provider_id>/.

A provider folder typically contains:

src/pycityvisitorparking/provider/<provider_id>/
  manifest.json
  __init__.py
  api.py
  const.py
  README.md
  CHANGELOG.md

Related docs:

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pycityvisitorparking-0.5.23.tar.gz (69.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pycityvisitorparking-0.5.23-py3-none-any.whl (66.3 kB view details)

Uploaded Python 3

File details

Details for the file pycityvisitorparking-0.5.23.tar.gz.

File metadata

  • Download URL: pycityvisitorparking-0.5.23.tar.gz
  • Upload date:
  • Size: 69.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pycityvisitorparking-0.5.23.tar.gz
Algorithm Hash digest
SHA256 be11d2d82d41fb60f8c3e66fb3a40a8ca80120692462a598f5dbe75c7c88da79
MD5 78db731259bdfa19719e9ea65af4700d
BLAKE2b-256 b430e9e78f7b76cba16f97730fd56d480880025a89d373af1a6fae6d7b784608

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycityvisitorparking-0.5.23.tar.gz:

Publisher: release.yml on sir-Unknown/pyCityVisitorParking

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pycityvisitorparking-0.5.23-py3-none-any.whl.

File metadata

File hashes

Hashes for pycityvisitorparking-0.5.23-py3-none-any.whl
Algorithm Hash digest
SHA256 40703578b40eb7bd1cc242fb373e314a7311ca4035424c5b732026e28ffa17e4
MD5 edc174dd144bafc1496674e53833f3d6
BLAKE2b-256 ed729b6db93d4cb63ea4ddb861c3860ad6e2f75bde0c346e9b88c238b138b545

See more details on using hashes here.

Provenance

The following attestation bundles were made for pycityvisitorparking-0.5.23-py3-none-any.whl:

Publisher: release.yml on sir-Unknown/pyCityVisitorParking

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page