Skip to content

Commit

Permalink
Merge branch 'develop' into 'main'
Browse files Browse the repository at this point in the history
Release Candidate 0.2.0

See merge request bcpearce/homeassistant-gtfs-realtime!20
  • Loading branch information
Benjamin Pearce committed Dec 22, 2024
2 parents 7004b10 + 9d873fd commit a6b746b
Show file tree
Hide file tree
Showing 28 changed files with 1,491 additions and 567 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,6 @@ home-assistant.log.fault
home-assistant_v2.db
home-assistant_v2.db-shm
home-assistant_v2.db-wal

# Ignore Misc
*.old
12 changes: 10 additions & 2 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ stages:
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
PRE_COMMIT_HOME: "/.cache/pre-commit"
PYTHON_IMAGE: python:3.12
PYTHON_IMAGE: python:3.13

include:
- local: "/template/.python-container.yml"
Expand All @@ -24,7 +24,15 @@ check_formatting:
paths:
- $PRE_COMMIT_HOME
script:
- pre-commit run --all-files
- SKIP=hassfest pre-commit run --all-files

hassfest:
stage: lint
image:
name: ghcr.io/home-assistant/hassfest
entrypoint: [""]
script:
- /usr/src/homeassistant/script/hassfest/docker/entrypoint.sh

pytest:
stage: test
Expand Down
30 changes: 18 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ repos:
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/psf/black
rev: "24.3.0"
hooks:
- id: black
args:
- --safe
- --quiet
files: ^((homeassistant|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
Expand All @@ -28,7 +20,7 @@ repos:
additional_dependencies:
- flake8-docstrings==1.5.0
- pydocstyle==5.0.2
files: ^(homeassistant|script|tests)/.+\.py$
files: ^(custom_components|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.8
hooks:
Expand All @@ -37,22 +29,36 @@ repos:
- --quiet
- --format=custom
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
files: ^(custom_components|script|tests)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.10.1
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: check-executables-have-shebangs
stages: [manual]
- id: check-json
- id: check-yaml
exclude: ^example/frontend.yaml
- id: pretty-format-json
args:
- --autofix
- --top-keys=domain,title,name
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.3
rev: v0.8.4
hooks:
# Run the linter.
- id: ruff
# Run the formatter.
- id: ruff-format

- repo: local
hooks:
- id: hassfest
name: hassfest
language: script
entry: hooks/hassfest.sh
files: ^(custom_components/.+/(icons|manifest|strings)\.json|custom_components/.+/translations/.+\.json|custom_components/.+/(quality_scale)\.yaml|custom_components/brands/.*\.json|custom_components/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
93 changes: 53 additions & 40 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,54 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Home Assistant",
"type": "debugpy",
"request": "launch",
"module": "homeassistant",
"justMyCode": false,
"args": [
"--debug",
"-c",
"."
]
},
{
"name": "Home Assistant (skip pip)",
"type": "debugpy",
"request": "launch",
"module": "homeassistant",
"justMyCode": false,
"args": [
"--debug",
"-c",
".",
"--skip-pip"
]
},
{
"name": "Home Assistant: Changed tests",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"justMyCode": false,
"args": [
"--timeout=10",
"--picked"
]
}
]
}
"configurations": [
{
"name": "Home Assistant",
"args": [
"--debug",
"-c",
"."
],
"env": {
"GTFS_REALTIME_SHOW_MEMORY_USE": "on"
},
"justMyCode": false,
"module": "homeassistant",
"request": "launch",
"type": "debugpy"
},
{
"name": "Home Assistant (skip pip)",
"args": [
"--debug",
"-c",
".",
"--skip-pip"
],
"justMyCode": false,
"module": "homeassistant",
"request": "launch",
"type": "debugpy"
},
{
"name": "Home Assistant: Changed tests",
"args": [
"--timeout=10",
"--picked"
],
"justMyCode": false,
"module": "pytest",
"request": "launch",
"type": "debugpy"
},
{
"name": "Python: Debug Tests",
"console": "integratedTerminal",
"justMyCode": false,
"purpose": [
"debug-test"
],
"request": "launch",
"type": "debugpy"
}
],
"version": "0.2.0"
}
1 change: 1 addition & 0 deletions custom_components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""GTFS Realtime HomeAssistant Custom Component."""
126 changes: 100 additions & 26 deletions custom_components/gtfs_realtime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
"""The GTFS Realtime integration."""

# GTFS Station Stop Feed Subject serves as the data hub for the integration

from datetime import timedelta
import logging
from typing import Any

from gtfs_station_stop.feed_subject import FeedSubject
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.selector import TextSelector, TextSelectorConfig
import voluptuous as vol

from custom_components.gtfs_realtime.config_flow import DOMAIN_SCHEMA

from .const import (
CAL_DB,
CLEAR_STATIC_FEEDS,
CONF_API_KEY,
CONF_GTFS_STATIC_DATA,
CONF_ROUTE_ICONS,
CONF_STATIC_SOURCES_UPDATE_FREQUENCY,
CONF_STATIC_SOURCES_UPDATE_FREQUENCY_DEFAULT,
CONF_URL_ENDPOINTS,
COORDINATOR_REALTIME,
DOMAIN,
RTI_DB,
SSI_DB,
TI_DB,
REFRESH_STATIC_FEEDS,
)
from .coordinator import GtfsRealtimeCoordinator

Expand All @@ -33,34 +35,106 @@
extra=vol.ALLOW_EXTRA,
)

type GtfsRealtimeConfigEntry = ConfigEntry[GtfsRealtimeCoordinator]

_LOGGER = logging.getLogger(__name__)


async def _async_create_gtfs_update_hub(hass: HomeAssistant, config: dict[str, Any]):
def create_gtfs_update_hub(
hass: HomeAssistant, config: dict[str, Any]
) -> GtfsRealtimeCoordinator:
"""Create the Update Coordinator."""
hub = FeedSubject(
config[CONF_URL_ENDPOINTS], headers={"api_key": config[CONF_API_KEY]}
)
route_icons = config.get(CONF_ROUTE_ICONS) # optional
# Attempt to perform an update to verify configuration
await hub.async_update()
coordinator_realtime = GtfsRealtimeCoordinator(
hass, hub, config[CONF_GTFS_STATIC_DATA], static_timedelta=timedelta(hours=24)
)
# Update the static data for the coordinator before the first update
await coordinator_realtime.async_update_static_data()
hass.data[DOMAIN] = {
COORDINATOR_REALTIME: coordinator_realtime,
CAL_DB: coordinator_realtime.calendar,
SSI_DB: coordinator_realtime.station_stop_info_db,
TI_DB: coordinator_realtime.trip_info_db,
RTI_DB: coordinator_realtime.route_info_db,
CONF_ROUTE_ICONS: route_icons,
route_icons: str | None = config.get(CONF_ROUTE_ICONS) # optional

static_timedelta = {
uri: timedelta(**timedelta_dict)
for uri, timedelta_dict in config[CONF_STATIC_SOURCES_UPDATE_FREQUENCY].items()
}
if CONF_ROUTE_ICONS in config:
hass.data[DOMAIN][CONF_ROUTE_ICONS] = config[CONF_ROUTE_ICONS]
return True
# if the value is 0, it is likely user input errors due to a bug in config flow UI, so coerce it to the default
for value in static_timedelta.values():
if value == timedelta(seconds=0):
value = timedelta(hours=CONF_STATIC_SOURCES_UPDATE_FREQUENCY_DEFAULT)
return GtfsRealtimeCoordinator(
hass,
hub,
config[CONF_GTFS_STATIC_DATA],
static_timedelta=static_timedelta,
route_icons=route_icons,
)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: GtfsRealtimeConfigEntry
) -> bool:
"""Set up GTFS Realtime Feed Subject for use by all sensors."""
await _async_create_gtfs_update_hub(hass, entry.data)
coordinator: GtfsRealtimeCoordinator = create_gtfs_update_hub(hass, entry.data)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator

async def handle_refresh_static_feeds(call):
"""Handle service action to refresh static feeds."""
entry.runtime_data.static_update_targets = set(call.data["gtfs_static_data"])
await entry.runtime_data.async_update_static_data()

async def handle_clear_static_feeds(call):
"""Handle service action to clear static feeds."""
await entry.runtime_data.async_update_static_data(clear_old_data=True)

hass.services.async_register(
DOMAIN,
REFRESH_STATIC_FEEDS,
handle_refresh_static_feeds,
vol.Schema(
{
vol.Optional(
CONF_GTFS_STATIC_DATA,
default=entry.runtime_data.gtfs_static_zip,
description=(
{"suggested_value": ["https://"]}
if len(entry.runtime_data.gtfs_static_zip) == 0
else {}
),
): TextSelector(TextSelectorConfig(multiline=False, multiple=True)),
}
),
)

hass.services.async_register(DOMAIN, CLEAR_STATIC_FEEDS, handle_clear_static_feeds)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload GTFS config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_migrate_entry(
hass: HomeAssistant, entry: GtfsRealtimeConfigEntry
) -> bool:
"""Migrate old entry."""
_LOGGER.debug(
"Migrating configuration from version %s.%s",
entry.version,
entry.minor_version,
)
if entry.version > 1:
return False
if entry.version == 1:
new_data = {**entry.data}
new_data[CONF_STATIC_SOURCES_UPDATE_FREQUENCY] = {}
for uri in new_data[CONF_GTFS_STATIC_DATA]:
_LOGGER.debug(
f"Static data source {uri} set to update on interval of {timedelta(seconds=CONF_STATIC_SOURCES_UPDATE_FREQUENCY_DEFAULT)}"
)
new_data[CONF_STATIC_SOURCES_UPDATE_FREQUENCY][uri] = {
"hours": CONF_STATIC_SOURCES_UPDATE_FREQUENCY_DEFAULT
}
hass.config_entries.async_update_entry(
entry, data=new_data, version=2, minor_version=0
)
return True
Loading

0 comments on commit a6b746b

Please sign in to comment.