velibdataset/velib.py

208 lines
6.9 KiB
Python
Executable File

#!/usr/bin/env python3
import datetime
import json
import logging
import os
import pybikes
import sqlite3
import time
level = logging.WARNING
if os.environ.get('DEBUG', False):
level = logging.DEBUG
logging.basicConfig(level=level)
def db_init(db_name=None):
"""
Initialize a database connection, initialize the tables.
Returns a new connection.
"""
logging.info('Initialize DB...')
now = datetime.datetime.now()
if db_name is None:
db_name = "week_%s.db" % now.strftime("%V")
db_folder = os.path.join(
'data',
now.strftime('%Y')
)
if not os.path.isdir(db_folder):
os.makedirs(db_folder)
conn = sqlite3.connect(os.path.join(db_folder, db_name))
c = conn.cursor()
# Init tables
c.execute("CREATE TABLE IF NOT EXISTS stations(" +
"id INTEGER, " +
"name TEXT, " +
"address TEXT, " +
"latitude REAL, " +
"longitude REAL, " +
"banking INTEGER, " +
"bonus INTEGER, " +
"bike_stands INTEGER)")
c.execute("CREATE TABLE IF NOT EXISTS stationsstats(" +
"station_id INTEGER, " +
"available_bikes INTEGER, " +
"available_ebikes INTEGER, " +
"free_stands INTEGER, " +
"status TEXT, " +
"updated INTEGER, " +
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE CASCADE)")
c.execute("CREATE TABLE IF NOT EXISTS stationsevents(" +
"station_id INTEGER, " +
"timestamp INTEGER, " +
"event TEXT, " +
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE CASCADE)")
c.execute("CREATE INDEX IF NOT EXISTS stationstats_station_id ON stationsstats (station_id)")
c.execute("CREATE INDEX IF NOT EXISTS stationsstats_updated ON stationsstats (updated)")
c.execute("CREATE INDEX IF NOT EXISTS stationsevents_station_id ON stationsevents (station_id)")
c.execute("CREATE INDEX IF NOT EXISTS stationsevents_timestamp ON stationsevents (timestamp)")
conn.commit()
return conn
def update_stations(conn):
"""
Update the stored station list.
:param conn: Database connection.
"""
logging.info('Get all stations from database...')
c = conn.cursor()
database_stations = {
i[0]: i
for i in c.execute(
"SELECT id, name, address, latitude, longitude, banking, bonus, bike_stands FROM stations"
).fetchall()
}
logging.info('Get updated Velib stations from the API...')
velib = pybikes.get("velib")
velib.update()
fetched_stations = {
station.extra['uid']: station
for station in velib.stations
if 'uid' in station.extra
}
# List of SQL queries to perform for
events = [] # events happening on stations (temporary closure etc)
stations_update = [] # Update of stations (such as new stands number)
new_stations = [] # New stations to add to the list
stats = [] # Current stats of the station
logging.info('Processing fetched stations...')
for uid, station in fetched_stations.items():
try:
old_station = database_stations[uid]
# Diff the two stations
event = []
if station.name != old_station[1]:
event.append({"key": "name",
"old_value": old_station[1],
"new_value": station.name})
if station.latitude != old_station[3]:
event.append({"key": "latitude",
"old_value": old_station[3],
"new_value": station.latitude})
if station.longitude != old_station[4]:
event.append({"key": "longitude",
"old_value": old_station[4],
"new_value": station.longitude})
if (
"banking" in station.extra
and station.extra["banking"] != old_station[5]
):
event.append({"key": "banking",
"old_value": old_station[5],
"new_value": station.extra["banking"]})
if (
"slots" in station.extra
and station.extra["slots"] != old_station[7]
):
event.append({"key": "bike_stands",
"old_value": old_station[7],
"new_value": station.extra["slots"]})
# If diff was found
if len(event) > 0:
stations_update.append(
(station.name,
station.latitude,
station.longitude,
station.extra["banking"],
station.extra["slots"],
uid)
)
events.append(
(uid, int(time.time()), json.dumps(event))
)
except KeyError:
# Add the station to the database
new_stations.append(
(uid,
station.name,
"", # Not available
station.latitude,
station.longitude,
station.extra["banking"],
False, # Not available
station.extra["slots"])
)
stats.append(
(uid,
station.bikes - station.extra["ebikes"],
station.extra["ebikes"],
station.free,
station.extra["status"],
int(time.time())) # Not available, using current timestamp
)
# Update stations
logging.info('Updating stations in db...')
c.executemany(
"UPDATE stations SET name=?, latitude=?, longitude=?, " +
"banking=?, bike_stands=? WHERE id=?",
stations_update
)
# Insert events in the table
logging.info('Storing stations events in db...')
c.executemany(
"INSERT INTO stationsevents(station_id, timestamp, event) " +
"VALUES(?, ?, ?)",
events
)
# Add the station to the database
logging.info('Storing new stations in db...')
c.executemany(
"INSERT INTO " +
"stations(id, name, address, latitude, longitude, banking, bonus, bike_stands) " +
"VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
new_stations
)
# Insert the current state in the db
logging.info('Storing current stations stats in db...')
c.executemany(
"INSERT INTO " +
"stationsstats(station_id, available_bikes, available_ebikes, free_stands, status, updated) " +
"VALUES(?, ?, ?, ?, ?, ?)",
stats
)
conn.commit()
def main():
"""
Handle main operations.
"""
# Get updated list of stations for smovengo
conn = db_init()
update_stations(conn)
if __name__ == "__main__":
main()