An unofficial Python module for querying Dresden's public transport system (VVO/DVB).
Want something like this for another language? Look here.
pip install dvb
from dvb import Client
client = Client(user_agent="my-app/1.0 (me@example.com)")
All API access goes through a Client instance, which requires a user_agent string identifying your project and providing contact details.
client.find("Helmholtzstraße")
[
Stop(id='33000742', name='Helmholtzstraße', city='', coords=Coords(lat=51.03, lng=13.73)),
Stop(id='36030083', name='Helmholtzstr', city='Chemnitz', coords=Coords(lat=50.83, lng=12.93)),
...
]
client.monitor("Helmholtzstraße", limit=2)
[
Departure(
id='voe:11003: :H:j26',
line='3',
direction='Wilder Mann',
scheduled=datetime(2025, 2, 22, 14, 41),
real_time=datetime(2025, 2, 22, 14, 43, 50),
state='Delayed',
platform=Platform(name='1', type='Platform'),
mode='Tram',
occupancy='ManySeats',
),
...
]
Stop names are automatically resolved to IDs. You can also pass a numeric stop ID directly.
client.route("Helmholtzstraße", "Postplatz")
[
Route(
duration=11,
interchanges=0,
price='2,30',
fare_zones='TZ 10 (Dresden)',
cancelled=False,
legs=[
PartialRoute(
duration=11,
line='3',
mode='Tram',
direction='Btf Trachenberge',
stops=[...],
)
],
session_id='367417461:efa4',
),
...
]
Use the session_id to paginate with client.earlier_later().
Search for stops, POIs, and other points of interest within a bounding box.
client.pins(51.04, 13.70, 51.05, 13.72, pin_types=("Stop", "Platform"))
[
Pin(id='33000028', name='Hauptbahnhof', city='Dresden', coords=Coords(...), type='Stop'),
Pin(id='pf:1234', name='Hauptbahnhof Gleis 3', city='Dresden', coords=Coords(...), type='Platform'),
...
]
client.lines("33000742")
[
Line(name='3', mode='Tram', directions=['Dresden Wilder Mann', 'Dresden Coschütz']),
Line(name='66', mode='CityBus', directions=['Dresden Lockwitz']),
...
]
client.route_changes()
[
RouteChange(
id='511595',
title='Dresden - Mengsstraße, Vollsperrung',
description='<p>...</p>',
type='Scheduled',
validity_periods=[ValidityPeriod(begin=..., end=...)],
lines=['428296'],
),
...
]
Get all stops for a specific departure (using the ID and time from a monitor response).
departure = client.monitor("Helmholtzstraße")[0]
client.trip_details(trip_id=departure.id, time=departure.scheduled, stop_id="33000742")
client.address(51.04373, 13.70320)
Stop(id='33000144', name='Tharandter Straße', city='Dresden', coords=Coords(...))
All methods accept raw=True to get the unprocessed API response as a dict:
client.monitor("Helmholtzstraße", raw=True)
# Returns the raw JSON dict from the WebAPI
from dvb import Client, APIError, ConnectionError
client = Client(user_agent="my-app/1.0 (me@example.com)")
try:
client.monitor("Helmholtzstraße")
except ConnectionError:
print("Network error or timeout")
except APIError:
print("API returned an error")
dvb 3.0 introduces a Client class that requires a user_agent string. This helps the DVB/VVO identify API consumers and provides them with a way to reach out if needed.
- All functions have moved from
dvb.function()toclient.function()on aClientinstance import dvb+dvb.monitor(...)→from dvb import Client+Client(user_agent="...").monitor(...)- All method signatures remain the same
dvb 2.0 was a complete rewrite with breaking changes:
- All functions return frozen dataclasses instead of dicts/lists
- Functions raise
APIError/ConnectionErrorinstead of printing errors and returningNone - Migrated from legacy widget/EFA endpoints to the VVO WebAPI
- Removed
poi_coords()(usefind()),interchange_prediction(),city/eduroam/deparrparameters route()usesarrival=True/Falseinstead ofdeparr="arr"/"dep",pins()usespin_types=("Stop",)instead ofpintypes="stop"- Dropped
numpy - Requires Python >= 3.10
uv sync --group dev
uv run ruff check .
uv run ruff format --check .
uv run mypy dvb/
uv run pytest