Skip to content

Commit

Permalink
Adds environment variable alternatives to config items (#210)
Browse files Browse the repository at this point in the history
* Adds environment variable alternatives to config items

* Updates changelog

* Fixes grammar

* Changes based on PR feedback

* Fixes import sorting

* Fixes tests

* Reverts previous change and black formatting
  • Loading branch information
joewesch authored Feb 4, 2024
1 parent b977b7e commit 6b29121
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ If there is no "Upgrading" header for that version, no post-upgrade actions need


## Upcoming
### New Features
- Added environment variable alternatives for many configuration items
([#210](https://github.com/jdholtz/auto-southwest-check-in/pull/210))

### Bug Fixes
- Fix failed logins not reporting the correct error
([#189](https://github.com/jdholtz/auto-southwest-check-in/issues/189))
Expand Down
37 changes: 30 additions & 7 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ file can be found at [config.example.json](config.example.json)
Auto-Southwest Check-In supports both global configuration and account/reservation-specific configuration. See
[Accounts and Reservations](#accounts-and-reservations) for more information.

**Note**: Many configuration items may also be configured via environment variables (except for account and
reservation specific configurations).

## Table of Contents
- [Fare Check](#fare-check)
- [Notifications](#notifications)
Expand All @@ -19,7 +22,9 @@ Auto-Southwest Check-In supports both global configuration and account/reservati

## Fare Check
Default: true \
Type: Boolean
Type: Boolean \
Environment Variable: `AUTO_SOUTHWEST_CHECK_IN_CHECK_FARES`
> Using the environment variable will override the applicable setting in `config.json`.
In addition to automatically checking in, check for price drops on an interval
(see [Retrieval Interval](#retrieval-interval)). If a lower fare is found, the user will be notified.
Expand All @@ -34,7 +39,10 @@ In addition to automatically checking in, check for price drops on an interval
## Notifications
### Notification URLs
Default: [] \
Type: String or List
Type: String or List \
Environment Variable: `AUTO_SOUTHWEST_CHECK_IN_NOTIFICATION_URL`
> When using the environment variable, you may only specify a single URL.
> If you are also using `config.json`, it will append the URL as long as it's not a duplicate.
Users can be notified on successful and failed check-ins. This is done through the [Apprise library][0].
To start, first gather the service URL you want to send notifications to (information on how to create
Expand All @@ -57,7 +65,9 @@ If you have more than one service you want to send notifications to, you can put

### Notification Level
Default: 1 \
Type: Integer
Type: Integer \
Environment Variable: `AUTO_SOUTHWEST_CHECK_IN_NOTIFICATION_LEVEL`
> Using the environment variable will override the applicable setting in `config.json`.
You can also select the level of notifications you want to receive.
```json
Expand All @@ -76,7 +86,9 @@ $ python3 southwest.py --test-notifications

## Browser Path
Default: The path to your Chrome or Chromium browser (if installed) \
Type: String
Type: String \
Environment Variable: `AUTO_SOUTHWEST_CHECK_IN_BROWSER_PATH`
> Using the environment variable will override the applicable setting in `config.json`.
If you use another Chromium-based browser besides Google Chrome or Chromium (such as Brave), you need to specify the path to
the browser executable.
Expand All @@ -90,7 +102,9 @@ the browser executable.

## Retrieval Interval
Default: 24 hours \
Type: Integer
Type: Integer \
Environment Variable: `AUTO_SOUTHWEST_CHECK_IN_RETRIEVAL_INTERVAL`
> Using the environment variable will override the applicable setting in `config.json`.
You can choose how often the script checks for lower fares on scheduled flights (in hours). Additionally, this
interval will also determine how often the script checks for new flights if login credentials are provided. To
Expand All @@ -108,7 +122,11 @@ account and reservation.

### Accounts
Default: [] \
Type: List
Type: List \
Environment Variables:
- `AUTO_SOUTHWEST_CHECK_IN_USERNAME`
- `AUTO_SOUTHWEST_CHECK_IN_PASSWORD`
> When using the environment variables, you may only specify a single set of credentials.
You can add more accounts to the script, allowing you to run multiple accounts at the same time and/or not
provide a username and password as arguments.
Expand All @@ -123,7 +141,12 @@ provide a username and password as arguments.

### Reservations
Default: [] \
Type: List
Type: List \
Environment Variables:
- `AUTO_SOUTHWEST_CHECK_IN_CONFIRMATION_NUMBER`
- `AUTO_SOUTHWEST_CHECK_IN_FIRST_NAME`
- `AUTO_SOUTHWEST_CHECK_IN_LAST_NAME`
> When using the environment variables, you may only specify a single reservation.
You can also add more reservations to the script, allowing you check in to multiple reservations in the same instance
and/or not provide reservation information as arguments.
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ Alternatively, you can log in to your account, which will automatically check yo
```shell
python3 southwest.py USERNAME PASSWORD
```
**Note**: If any arguments contain special characters, make sure to escape them so they are passed into
the script correctly.
**Note**: If any arguments contain special characters, make sure to escape them or use
environment variables so they are passed into the script correctly.

For the full usage of the script, run:
```shell
Expand Down Expand Up @@ -97,7 +97,7 @@ You can optionally attach a configuration file to the container by adding the

**Note**: The recommended restart policy for the container is `on-failure` or `no`

#### Docker Compose Example
#### Docker Compose Example Using Config
```yaml
services:
auto-southwest:
Expand All @@ -108,6 +108,18 @@ services:
- /full-path/to/config.json:/app/config.json
```
#### Docker Compose Example Using Environment Variables
```yaml
services:
auto-southwest:
image: jdholtz/auto-southwest-check-in
container_name: auto-southwest
restart: on-failure
environment:
- AUTO_SOUTHWEST_CHECK_IN_USERNAME=MyUsername
- AUTO_SOUTHWEST_CHECK_IN_PASSWORD=TopsyKretts
```
Additional information on the Docker container can be found in the [public repository][5].
## Configuration
Expand Down
73 changes: 72 additions & 1 deletion lib/config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List

from .log import get_logger
from .utils import NotificationLevel
from .utils import NotificationLevel, is_truthy

# Type alias for JSON
JSON = Dict[str, Any]
Expand Down Expand Up @@ -108,6 +109,7 @@ def initialize(self) -> JSON:

try:
config = self._read_config()
config = self._read_env_vars(config)
self._parse_config(config)
except (ConfigError, json.decoder.JSONDecodeError) as err:
print("Error in configuration file:")
Expand Down Expand Up @@ -144,6 +146,75 @@ def _read_config(self) -> JSON:

return config

def _read_env_vars(self, config: JSON) -> JSON:
logger.debug("Reading configuration from environment variables")
# Check Fares
check_fares = os.getenv("AUTO_SOUTHWEST_CHECK_IN_CHECK_FARES")
if check_fares:
try:
config["check_fares"] = is_truthy(check_fares)
except ValueError as err:
raise ConfigError("Error parsing 'AUTO_SOUTHWEST_CHECK_IN_CHECK_FARES'") from err

# Notification URL
notification_url = os.getenv("AUTO_SOUTHWEST_CHECK_IN_NOTIFICATION_URL")
if notification_url:
config.setdefault("notification_urls", [])
if isinstance(config["notification_urls"], str):
config["notification_urls"] = [config["notification_urls"]]
if not isinstance(config["notification_urls"], list):
raise ConfigError("'notification_urls' must be a string or a list")
if notification_url not in config["notification_urls"]:
config["notification_urls"].append(notification_url)

# Notification Level
notification_level = os.getenv("AUTO_SOUTHWEST_CHECK_IN_NOTIFICATION_LEVEL")
if notification_level:
try:
config["notification_level"] = int(notification_level)
except ValueError as err:
raise ConfigError(
"'AUTO_SOUTHWEST_CHECK_IN_NOTIFICATION_LEVEL' must be an integer"
) from err

# Browser Path
browser_path = os.getenv("AUTO_SOUTHWEST_CHECK_IN_BROWSER_PATH")
if browser_path:
config["browser_path"] = browser_path

# Retrieval Interval
retrieval_interval = os.getenv("AUTO_SOUTHWEST_CHECK_IN_RETRIEVAL_INTERVAL")
if retrieval_interval:
try:
config["retrieval_interval"] = int(retrieval_interval)
except ValueError as err:
raise ConfigError(
"'AUTO_SOUTHWEST_CHECK_IN_RETRIEVAL_INTERVAL' must be an integer"
) from err

# Account credentials
username = os.getenv("AUTO_SOUTHWEST_CHECK_IN_USERNAME")
password = os.getenv("AUTO_SOUTHWEST_CHECK_IN_PASSWORD")
if username and password:
new_credentials = {"username": username, "password": password}
config.setdefault("accounts", [])
config["accounts"].append(new_credentials)

# Reservation information
confirmation_number = os.getenv("AUTO_SOUTHWEST_CHECK_IN_CONFIRMATION_NUMBER")
first_name = os.getenv("AUTO_SOUTHWEST_CHECK_IN_FIRST_NAME")
last_name = os.getenv("AUTO_SOUTHWEST_CHECK_IN_LAST_NAME")
if confirmation_number and first_name and last_name:
new_reservation = {
"confirmationNumber": confirmation_number,
"firstName": first_name,
"lastName": last_name,
}
config.setdefault("reservations", [])
config["reservations"].append(new_reservation)

return config

def _parse_config(self, config: JSON) -> None:
super()._parse_config(config)

Expand Down
25 changes: 24 additions & 1 deletion lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
import time
from enum import IntEnum
from typing import Any, Dict
from typing import Any, Dict, Union

import requests

Expand Down Expand Up @@ -73,3 +73,26 @@ class FlightChangeError(Exception):
class NotificationLevel(IntEnum):
INFO = 1
ERROR = 2


def is_truthy(arg: Union[bool, int, str]) -> bool:
"""
Convert "truthy" strings into Booleans.
Examples:
>>> is_truthy('yes')
True
Args:
arg: Truthy value (True values are y, yes, t, true, on and 1; false values are n, no,
f, false, off and 0. Raises ValueError if val is anything else.
"""
if isinstance(arg, bool):
return arg

val = str(arg).lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
if val in ("n", "no", "f", "false", "off", "0"):
return False
raise ValueError(f"Invalid truthy value: `{arg}`")
Loading

0 comments on commit 6b29121

Please sign in to comment.