From 90f1b57ed80440a62c704a772de4889f440be7a1 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:41:09 -0700 Subject: [PATCH 01/10] Initial GTFS sensor --- homeassistant/components/sensor/gtfs.py | 236 ++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 homeassistant/components/sensor/gtfs.py diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py new file mode 100644 index 00000000000..9271df28820 --- /dev/null +++ b/homeassistant/components/sensor/gtfs.py @@ -0,0 +1,236 @@ +""" +Support for GTFS (Google/General Transport Format Schema). + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.gtfs/ +""" +import os +import logging +import datetime + +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ["SQLAlchemy", "https://github.com/jarondl/pygtfs/archive/" + "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" + "pygtfs==0.1.2"] + +ICON = "mdi:train" + +TIME_FORMAT = "%Y-%m-%d %H:%M:%S" + +def get_next_departure(sched, start_station_id, end_station_id): + origin_station = sched.stops_by_id(start_station_id)[0] + destination_station = sched.stops_by_id(end_station_id)[0] + + now = datetime.datetime.now() + day_name = now.strftime("%A").lower() + now_str = now.strftime("%H:%M:%S") + + sql_query = """ + SELECT trip.trip_id, trip.route_id, + time(origin_stop_time.departure_time), + time(destination_stop_time.arrival_time), + time(origin_stop_time.arrival_time), + time(origin_stop_time.departure_time), + origin_stop_time.drop_off_type, + origin_stop_time.pickup_type, + origin_stop_time.shape_dist_traveled, + origin_stop_time.stop_headsign, + origin_stop_time.stop_sequence, + time(destination_stop_time.arrival_time), + time(destination_stop_time.departure_time), + destination_stop_time.drop_off_type, + destination_stop_time.pickup_type, + destination_stop_time.shape_dist_traveled, + destination_stop_time.stop_headsign, + destination_stop_time.stop_sequence + FROM trips trip + INNER JOIN calendar calendar + ON trip.service_id = calendar.service_id + INNER JOIN stop_times origin_stop_time + ON trip.trip_id = origin_stop_time.trip_id + INNER JOIN stops start_station + ON origin_stop_time.stop_id = start_station.stop_id + INNER JOIN stop_times destination_stop_time + ON trip.trip_id = destination_stop_time.trip_id + INNER JOIN stops end_station + ON destination_stop_time.stop_id = end_station.stop_id + WHERE calendar.{} = 1 AND time(origin_stop_time.departure_time) > time('{}') + AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' + ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ + .format(day_name, now_str, origin_station.id, destination_station.id) + result = sched.engine.execute(sql_query) + item = {} + for row in result: + item = row + + today = datetime.datetime.today().strftime("%Y-%m-%d") + departure_time_string = "{} {}".format(today, item[2]) + arrival_time_string = "{} {}".format(today, item[3]) + departure_time = datetime.datetime.strptime(departure_time_string, + TIME_FORMAT) + arrival_time = datetime.datetime.strptime(arrival_time_string, + TIME_FORMAT) + + seconds_until = (departure_time-datetime.datetime.now()).total_seconds() + minutes_until = int(seconds_until / 60) + + route = sched.routes_by_id(item[1])[0] + + origin_stop_time_arrival_time = "{} {}".format(today, item[4]) + + origin_stop_time_departure_time = "{} {}".format(today, item[5]) + + destination_stop_time_arrival_time = "{} {}".format(today, item[11]) + + destination_stop_time_departure_time = "{} {}".format(today, item[12]) + + origin_stop_time_dict = { + "Arrival Time": origin_stop_time_arrival_time, + "Departure Time": origin_stop_time_departure_time, + "Drop Off Type": item[6], "Pickup Type": item[7], + "Shape Dist Traveled": item[8], "Headsign": item[9], + "Sequence": item[10] + } + + destination_stop_time_dict = { + "Arrival Time": destination_stop_time_arrival_time, + "Departure Time": destination_stop_time_departure_time, + "Drop Off Type": item[13], "Pickup Type": item[14], + "Shape Dist Traveled": item[15], "Headsign": item[16], + "Sequence": item[17] + } + + return { + "trip_id": item[0], + "trip": sched.trips_by_id(item[0])[0], + "route": route, + "agency": sched.agencies_by_id(route.agency_id)[0], + "origin_station": origin_station, "departure_time": departure_time, + "destination_station": destination_station, "arrival_time": arrival_time, + "seconds_until_departure": seconds_until, + "minutes_until_departure": minutes_until, + "origin_stop_time": origin_stop_time_dict, + "destination_stop_time": destination_stop_time_dict + } + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Get the GTFS sensor.""" + if config.get("origin") is None: + _LOGGER.error("Origin must be set in the GTFS configuration!") + return False + + if config.get("destination") is None: + _LOGGER.error("Destination must be set in the GTFS configuration!") + return False + + if config.get("data") is None: + _LOGGER.error("Data must be set in the GTFS configuration!") + return False + + dev = [] + dev.append(GTFSDepartureSensor(config["data"], hass.config.path("gtfs"), + config["origin"], config["destination"])) + add_devices(dev) + +# pylint: disable=too-few-public-methods +class GTFSDepartureSensor(Entity): + """Implementation of an GTFS departures sensor.""" + + def __init__(self, data_source, gtfs_folder, origin, destination): + """Initialize the sensor.""" + self._data_source = data_source + self._gtfs_folder = gtfs_folder + self.origin = origin + self.destination = destination + self._name = "GTFS Sensor" + self._unit_of_measurement = "min" + self._state = 0 + self._attributes = {} + self.update() + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON + + def update(self): + """Get the latest data from GTFS and update the states.""" + import pygtfs + + split_file_name = os.path.splitext(self._data_source) + + sqlite_file = "{}.sqlite".format(split_file_name[0]) + gtfs = pygtfs.Schedule(os.path.join(self._gtfs_folder, sqlite_file)) + + if len(gtfs.feeds) < 1: + pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, + self._data_source)) + + self._departure = get_next_departure(gtfs, self.origin, + self.destination) + self._state = self._departure["minutes_until_departure"] + + origin_station = self._departure["origin_station"] + destination_station = self._departure["destination_station"] + origin_stop_time = self._departure["origin_stop_time"] + destination_stop_time = self._departure["destination_stop_time"] + agency = self._departure["agency"] + route = self._departure["route"] + trip = self._departure["trip"] + + name = "{} {} to {} next departure" + self._name = name.format(agency.agency_name, + origin_station.stop_id, + destination_station.stop_id) + + # Build attributes + + self._attributes = {} + + def dictForTable(resource): + return dict((col, getattr(resource, col)) \ + for col in resource.__table__.columns.keys()) + + def appendKeys(resource, prefix=None): + for key, val in resource.items(): + if val == "" or val is None or key == "feed_id": + continue + prettyKey = key.replace("_", " ") + prettyKey = prettyKey.title() + prettyKey = prettyKey.replace("Id", "ID") + prettyKey = prettyKey.replace("Url", "URL") + if prefix is not None and prettyKey.startswith(prefix) is False: + prettyKey = "{} {}".format(prefix, prettyKey) + self._attributes[prettyKey] = val + + appendKeys(dictForTable(agency), "Agency") + appendKeys(dictForTable(route), "Route") + appendKeys(dictForTable(trip), "Trip") + appendKeys(dictForTable(origin_station), "Origin Station") + appendKeys(dictForTable(destination_station), "Destination Station") + appendKeys(origin_stop_time, "Origin Stop") + appendKeys(destination_stop_time, "Destination Stop") From 8fe1a9f008e4390a109749afcacbadc69262fc5d Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:53:46 -0700 Subject: [PATCH 02/10] Pylint and flake8 --- homeassistant/components/sensor/gtfs.py | 225 +++++++++++++----------- 1 file changed, 119 insertions(+), 106 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 9271df28820..16d7268afa3 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -9,7 +9,6 @@ import logging import datetime from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -21,101 +20,109 @@ ICON = "mdi:train" TIME_FORMAT = "%Y-%m-%d %H:%M:%S" +# pylint: disable=too-many-locals + + def get_next_departure(sched, start_station_id, end_station_id): - origin_station = sched.stops_by_id(start_station_id)[0] - destination_station = sched.stops_by_id(end_station_id)[0] + """Get the next departure for the given sched.""" + origin_station = sched.stops_by_id(start_station_id)[0] + destination_station = sched.stops_by_id(end_station_id)[0] - now = datetime.datetime.now() - day_name = now.strftime("%A").lower() - now_str = now.strftime("%H:%M:%S") + now = datetime.datetime.now() + day_name = now.strftime("%A").lower() + now_str = now.strftime("%H:%M:%S") - sql_query = """ - SELECT trip.trip_id, trip.route_id, - time(origin_stop_time.departure_time), - time(destination_stop_time.arrival_time), - time(origin_stop_time.arrival_time), - time(origin_stop_time.departure_time), - origin_stop_time.drop_off_type, - origin_stop_time.pickup_type, - origin_stop_time.shape_dist_traveled, - origin_stop_time.stop_headsign, - origin_stop_time.stop_sequence, - time(destination_stop_time.arrival_time), - time(destination_stop_time.departure_time), - destination_stop_time.drop_off_type, - destination_stop_time.pickup_type, - destination_stop_time.shape_dist_traveled, - destination_stop_time.stop_headsign, - destination_stop_time.stop_sequence - FROM trips trip - INNER JOIN calendar calendar - ON trip.service_id = calendar.service_id - INNER JOIN stop_times origin_stop_time - ON trip.trip_id = origin_stop_time.trip_id - INNER JOIN stops start_station - ON origin_stop_time.stop_id = start_station.stop_id - INNER JOIN stop_times destination_stop_time - ON trip.trip_id = destination_stop_time.trip_id - INNER JOIN stops end_station - ON destination_stop_time.stop_id = end_station.stop_id - WHERE calendar.{} = 1 AND time(origin_stop_time.departure_time) > time('{}') - AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' - ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ - .format(day_name, now_str, origin_station.id, destination_station.id) - result = sched.engine.execute(sql_query) - item = {} - for row in result: - item = row + sql_query = """ + SELECT trip.trip_id, trip.route_id, + time(origin_stop_time.departure_time), + time(destination_stop_time.arrival_time), + time(origin_stop_time.arrival_time), + time(origin_stop_time.departure_time), + origin_stop_time.drop_off_type, + origin_stop_time.pickup_type, + origin_stop_time.shape_dist_traveled, + origin_stop_time.stop_headsign, + origin_stop_time.stop_sequence, + time(destination_stop_time.arrival_time), + time(destination_stop_time.departure_time), + destination_stop_time.drop_off_type, + destination_stop_time.pickup_type, + destination_stop_time.shape_dist_traveled, + destination_stop_time.stop_headsign, + destination_stop_time.stop_sequence + FROM trips trip + INNER JOIN calendar calendar + ON trip.service_id = calendar.service_id + INNER JOIN stop_times origin_stop_time + ON trip.trip_id = origin_stop_time.trip_id + INNER JOIN stops start_station + ON origin_stop_time.stop_id = start_station.stop_id + INNER JOIN stop_times destination_stop_time + ON trip.trip_id = destination_stop_time.trip_id + INNER JOIN stops end_station + ON destination_stop_time.stop_id = end_station.stop_id + WHERE calendar.{} = 1 + AND time(origin_stop_time.departure_time) > time('{}') + AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' + ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ + .format(day_name, now_str, origin_station.id, destination_station.id) + result = sched.engine.execute(sql_query) + item = {} + for row in result: + item = row - today = datetime.datetime.today().strftime("%Y-%m-%d") - departure_time_string = "{} {}".format(today, item[2]) - arrival_time_string = "{} {}".format(today, item[3]) - departure_time = datetime.datetime.strptime(departure_time_string, + today = datetime.datetime.today().strftime("%Y-%m-%d") + departure_time_string = "{} {}".format(today, item[2]) + arrival_time_string = "{} {}".format(today, item[3]) + departure_time = datetime.datetime.strptime(departure_time_string, + TIME_FORMAT) + arrival_time = datetime.datetime.strptime(arrival_time_string, TIME_FORMAT) - arrival_time = datetime.datetime.strptime(arrival_time_string, - TIME_FORMAT) - seconds_until = (departure_time-datetime.datetime.now()).total_seconds() - minutes_until = int(seconds_until / 60) + seconds_until = (departure_time-datetime.datetime.now()).total_seconds() + minutes_until = int(seconds_until / 60) - route = sched.routes_by_id(item[1])[0] + route = sched.routes_by_id(item[1])[0] - origin_stop_time_arrival_time = "{} {}".format(today, item[4]) + origin_stoptime_arrival_time = "{} {}".format(today, item[4]) - origin_stop_time_departure_time = "{} {}".format(today, item[5]) + origin_stoptime_departure_time = "{} {}".format(today, item[5]) - destination_stop_time_arrival_time = "{} {}".format(today, item[11]) + dest_stoptime_arrival_time = "{} {}".format(today, item[11]) - destination_stop_time_departure_time = "{} {}".format(today, item[12]) + dest_stoptime_depart_time = "{} {}".format(today, item[12]) - origin_stop_time_dict = { - "Arrival Time": origin_stop_time_arrival_time, - "Departure Time": origin_stop_time_departure_time, - "Drop Off Type": item[6], "Pickup Type": item[7], - "Shape Dist Traveled": item[8], "Headsign": item[9], - "Sequence": item[10] - } + origin_stop_time_dict = { + "Arrival Time": origin_stoptime_arrival_time, + "Departure Time": origin_stoptime_departure_time, + "Drop Off Type": item[6], "Pickup Type": item[7], + "Shape Dist Traveled": item[8], "Headsign": item[9], + "Sequence": item[10] + } - destination_stop_time_dict = { - "Arrival Time": destination_stop_time_arrival_time, - "Departure Time": destination_stop_time_departure_time, - "Drop Off Type": item[13], "Pickup Type": item[14], - "Shape Dist Traveled": item[15], "Headsign": item[16], - "Sequence": item[17] - } + destination_stop_time_dict = { + "Arrival Time": dest_stoptime_arrival_time, + "Departure Time": dest_stoptime_depart_time, + "Drop Off Type": item[13], "Pickup Type": item[14], + "Shape Dist Traveled": item[15], "Headsign": item[16], + "Sequence": item[17] + } + + return { + "trip_id": item[0], + "trip": sched.trips_by_id(item[0])[0], + "route": route, + "agency": sched.agencies_by_id(route.agency_id)[0], + "origin_station": origin_station, + "departure_time": departure_time, + "destination_station": destination_station, + "arrival_time": arrival_time, + "seconds_until_departure": seconds_until, + "minutes_until_departure": minutes_until, + "origin_stop_time": origin_stop_time_dict, + "destination_stop_time": destination_stop_time_dict + } - return { - "trip_id": item[0], - "trip": sched.trips_by_id(item[0])[0], - "route": route, - "agency": sched.agencies_by_id(route.agency_id)[0], - "origin_station": origin_station, "departure_time": departure_time, - "destination_station": destination_station, "arrival_time": arrival_time, - "seconds_until_departure": seconds_until, - "minutes_until_departure": minutes_until, - "origin_stop_time": origin_stop_time_dict, - "destination_stop_time": destination_stop_time_dict - } def setup_platform(hass, config, add_devices, discovery_info=None): """Get the GTFS sensor.""" @@ -136,7 +143,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config["origin"], config["destination"])) add_devices(dev) -# pylint: disable=too-few-public-methods +# pylint: disable=too-many-instance-attributes,too-few-public-methods + + class GTFSDepartureSensor(Entity): """Implementation of an GTFS departures sensor.""" @@ -186,9 +195,10 @@ class GTFSDepartureSensor(Entity): sqlite_file = "{}.sqlite".format(split_file_name[0]) gtfs = pygtfs.Schedule(os.path.join(self._gtfs_folder, sqlite_file)) + # pylint: disable=no-member if len(gtfs.feeds) < 1: - pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, - self._data_source)) + pygtfs.append_feed(gtfs, os.path.join(self._gtfs_folder, + self._data_source)) self._departure = get_next_departure(gtfs, self.origin, self.destination) @@ -211,26 +221,29 @@ class GTFSDepartureSensor(Entity): self._attributes = {} - def dictForTable(resource): - return dict((col, getattr(resource, col)) \ - for col in resource.__table__.columns.keys()) + def dict_for_table(resource): + """Return a dict for the SQLAlchemy resource given.""" + return dict((col, getattr(resource, col)) + for col in resource.__table__.columns.keys()) - def appendKeys(resource, prefix=None): - for key, val in resource.items(): - if val == "" or val is None or key == "feed_id": - continue - prettyKey = key.replace("_", " ") - prettyKey = prettyKey.title() - prettyKey = prettyKey.replace("Id", "ID") - prettyKey = prettyKey.replace("Url", "URL") - if prefix is not None and prettyKey.startswith(prefix) is False: - prettyKey = "{} {}".format(prefix, prettyKey) - self._attributes[prettyKey] = val + def append_keys(resource, prefix=None): + """Properly format key val pairs to append to attributes""" + for key, val in resource.items(): + if val == "" or val is None or key == "feed_id": + continue + pretty_key = key.replace("_", " ") + pretty_key = pretty_key.title() + pretty_key = pretty_key.replace("Id", "ID") + pretty_key = pretty_key.replace("Url", "URL") + if prefix is not None and \ + pretty_key.startswith(prefix) is False: + pretty_key = "{} {}".format(prefix, pretty_key) + self._attributes[pretty_key] = val - appendKeys(dictForTable(agency), "Agency") - appendKeys(dictForTable(route), "Route") - appendKeys(dictForTable(trip), "Trip") - appendKeys(dictForTable(origin_station), "Origin Station") - appendKeys(dictForTable(destination_station), "Destination Station") - appendKeys(origin_stop_time, "Origin Stop") - appendKeys(destination_stop_time, "Destination Stop") + append_keys(dict_for_table(agency), "Agency") + append_keys(dict_for_table(route), "Route") + append_keys(dict_for_table(trip), "Trip") + append_keys(dict_for_table(origin_station), "Origin Station") + append_keys(dict_for_table(destination_station), "Destination Station") + append_keys(origin_stop_time, "Origin Stop") + append_keys(destination_stop_time, "Destination Stop") From 8a5f60ee23637c6448cdf1ec2f4ce3b974093d3e Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:54:48 -0700 Subject: [PATCH 03/10] .coveragerc and requirements_all.txt --- .coveragerc | 1 + requirements_all.txt | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.coveragerc b/.coveragerc index 370d229d87d..3656ad8fde5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -136,6 +136,7 @@ omit = homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/forecast.py homeassistant/components/sensor/glances.py + homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/netatmo.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/onewire.py diff --git a/requirements_all.txt b/requirements_all.txt index f7085806dc3..bae7c6475d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -15,6 +15,9 @@ PyMata==2.07a # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 +# homeassistant.components.sensor.gtfs +SQLAlchemy + # homeassistant.components.media_player.sonos SoCo==0.11.1 @@ -87,6 +90,9 @@ https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_ri # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 +# homeassistant.components.sensor.gtfs +https://github.com/jarondl/pygtfs/archive/d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#pygtfs==0.1.2 + # homeassistant.components.ecobee https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4 From 982baaba22af1a171b3c8bc7e6c697ee0908f8c2 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 17:59:38 -0700 Subject: [PATCH 04/10] Annoying missing period --- homeassistant/components/sensor/gtfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 16d7268afa3..0d6ac803abb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -227,7 +227,7 @@ class GTFSDepartureSensor(Entity): for col in resource.__table__.columns.keys()) def append_keys(resource, prefix=None): - """Properly format key val pairs to append to attributes""" + """Properly format key val pairs to append to attributes.""" for key, val in resource.items(): if val == "" or val is None or key == "feed_id": continue From f31ba1186107ffd9ce77cae291bb46699487b6fc Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:05:34 -0700 Subject: [PATCH 05/10] Lock SQLAlchemy --- homeassistant/components/sensor/gtfs.py | 3 ++- requirements_all.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 0d6ac803abb..60996baaa41 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -12,7 +12,8 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["SQLAlchemy", "https://github.com/jarondl/pygtfs/archive/" +REQUIREMENTS = ["SQLAlchemy==1.0.12", + "https://github.com/jarondl/pygtfs/archive/" "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" "pygtfs==0.1.2"] diff --git a/requirements_all.txt b/requirements_all.txt index bae7c6475d6..039183eebe7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -16,7 +16,7 @@ PyMata==2.07a # RPi.GPIO==0.6.1 # homeassistant.components.sensor.gtfs -SQLAlchemy +SQLAlchemy==1.0.12 # homeassistant.components.media_player.sonos SoCo==0.11.1 From 116b83b53ff8049b4f65fa1d0549b6d7663cafd4 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:25:49 -0700 Subject: [PATCH 06/10] Fix PyGTFS import errors --- homeassistant/components/sensor/gtfs.py | 5 ++--- requirements_all.txt | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 60996baaa41..97eaaa5bdeb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -12,9 +12,8 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ["SQLAlchemy==1.0.12", - "https://github.com/jarondl/pygtfs/archive/" - "d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#" +REQUIREMENTS = ["https://github.com/robbiet480/pygtfs/archive/" + "6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#" "pygtfs==0.1.2"] ICON = "mdi:train" diff --git a/requirements_all.txt b/requirements_all.txt index 039183eebe7..386c2d8473b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -15,9 +15,6 @@ PyMata==2.07a # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 -# homeassistant.components.sensor.gtfs -SQLAlchemy==1.0.12 - # homeassistant.components.media_player.sonos SoCo==0.11.1 @@ -90,9 +87,6 @@ https://github.com/denismakogon/rides-python-sdk/archive/py3-support.zip#uber_ri # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 -# homeassistant.components.sensor.gtfs -https://github.com/jarondl/pygtfs/archive/d6aea616e50a0f412b90c37dd7808296f1a6d881.zip#pygtfs==0.1.2 - # homeassistant.components.ecobee https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4 @@ -102,6 +96,9 @@ https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f # homeassistant.components.sensor.temper https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip#temperusb==1.2.3 +# homeassistant.components.sensor.gtfs +https://github.com/robbiet480/pygtfs/archive/6b40d5fb30fd410cfaf637c901b5ed5a08c33e4c.zip#pygtfs==0.1.2 + # homeassistant.components.scene.hunterdouglas_powerview https://github.com/sander76/powerviewApi/archive/master.zip#powerviewApi==0.2 From 1fd96296f7fddb94c6e7d7d0cb832ef1c2e36007 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:47:54 -0700 Subject: [PATCH 07/10] Use parameterized queries when possible --- homeassistant/components/sensor/gtfs.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 97eaaa5bdeb..bccf0ef2abb 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -32,7 +32,9 @@ def get_next_departure(sched, start_station_id, end_station_id): day_name = now.strftime("%A").lower() now_str = now.strftime("%H:%M:%S") - sql_query = """ + from sqlalchemy.sql import text + + sql_query = text(""" SELECT trip.trip_id, trip.route_id, time(origin_stop_time.departure_time), time(destination_stop_time.arrival_time), @@ -62,11 +64,13 @@ def get_next_departure(sched, start_station_id, end_station_id): INNER JOIN stops end_station ON destination_stop_time.stop_id = end_station.stop_id WHERE calendar.{} = 1 - AND time(origin_stop_time.departure_time) > time('{}') - AND start_station.stop_id = '{}' AND end_station.stop_id = '{}' - ORDER BY origin_stop_time.departure_time LIMIT 1;"""\ - .format(day_name, now_str, origin_station.id, destination_station.id) - result = sched.engine.execute(sql_query) + AND time(origin_stop_time.departure_time) > time(:now_str) + AND start_station.stop_id = :origin_station_id + AND end_station.stop_id = :end_station_id + ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(day_name)) + result = sched.engine.execute(sql_query,now_str=now_str, + origin_station_id=origin_station.id, + end_station_id=destination_station.id) item = {} for row in result: item = row From 5a35e4a9baa753e5847b4d9333506713c2f5b304 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:52:03 -0700 Subject: [PATCH 08/10] Data source validation --- homeassistant/components/sensor/gtfs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index bccf0ef2abb..9e198e7ef11 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -142,8 +142,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error("Data must be set in the GTFS configuration!") return False + gtfs_dir = hass.config.path("gtfs") + + if not os.path.exists(gtfs_dir): + os.makedirs(gtfs_dir) + + if not os.path.exists(os.path.join(gtfs_dir, config["data"])): + _LOGGER.error("The given GTFS data file/folder was not found!") + return False + dev = [] - dev.append(GTFSDepartureSensor(config["data"], hass.config.path("gtfs"), + dev.append(GTFSDepartureSensor(config["data"], gtfs_dir, config["origin"], config["destination"])) add_devices(dev) From dac3c9d1b568355aa949dea09677e94322c424cf Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:53:21 -0700 Subject: [PATCH 09/10] Pylint and flake8 --- homeassistant/components/sensor/gtfs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 9e198e7ef11..1ecbe9e9db4 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -68,7 +68,7 @@ def get_next_departure(sched, start_station_id, end_station_id): AND start_station.stop_id = :origin_station_id AND end_station.stop_id = :end_station_id ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(day_name)) - result = sched.engine.execute(sql_query,now_str=now_str, + result = sched.engine.execute(sql_query, now_str=now_str, origin_station_id=origin_station.id, end_station_id=destination_station.id) item = {} @@ -145,11 +145,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): gtfs_dir = hass.config.path("gtfs") if not os.path.exists(gtfs_dir): - os.makedirs(gtfs_dir) + os.makedirs(gtfs_dir) if not os.path.exists(os.path.join(gtfs_dir, config["data"])): - _LOGGER.error("The given GTFS data file/folder was not found!") - return False + _LOGGER.error("The given GTFS data file/folder was not found!") + return False dev = [] dev.append(GTFSDepartureSensor(config["data"], gtfs_dir, From 2a194d8861fd6484a601df5f6458c961c7e846a8 Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 26 Mar 2016 18:55:49 -0700 Subject: [PATCH 10/10] Use named string formatting for safety --- homeassistant/components/sensor/gtfs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 1ecbe9e9db4..4cd1655e6dc 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -63,11 +63,12 @@ def get_next_departure(sched, start_station_id, end_station_id): ON trip.trip_id = destination_stop_time.trip_id INNER JOIN stops end_station ON destination_stop_time.stop_id = end_station.stop_id - WHERE calendar.{} = 1 + WHERE calendar.{day_name} = 1 AND time(origin_stop_time.departure_time) > time(:now_str) AND start_station.stop_id = :origin_station_id AND end_station.stop_id = :end_station_id - ORDER BY origin_stop_time.departure_time LIMIT 1;""".format(day_name)) + ORDER BY origin_stop_time.departure_time LIMIT 1; + """.format(day_name=day_name)) result = sched.engine.execute(sql_query, now_str=now_str, origin_station_id=origin_station.id, end_station_id=destination_station.id)