From 0071fea8a3fef73b90776fbe4790a375eb4594c6 Mon Sep 17 00:00:00 2001 From: Justin Flannery <49741340+juftin@users.noreply.github.com> Date: Tue, 1 Jun 2021 19:15:00 -0600 Subject: [PATCH] camply 0.1.4 (#17) * update pypi secrets * YML Integration * YML update * TOC Entry * pushbullet * CHANGELOG.md + README.docker.md * YAML example update * Docker README Update * clickable logo * autoformat --- .github/workflows/camply-publishing.yml | 2 +- CHANGELOG.md | 13 + Dockerfile | 2 +- README.md | 87 ++- camply/__init__.py | 2 +- camply/config/__init__.py | 2 +- camply/config/api_config.py | 2 +- camply/config/cli_config.py | 16 +- camply/config/file_config.py | 1 + camply/config/notification_config.py | 16 +- camply/notifications/__init__.py | 4 +- camply/notifications/pushbullet.py | 91 +++ camply/utils/camply_cli.py | 57 +- camply/utils/configure_camply.py | 2 +- camply/utils/yaml_utils.py | 112 ++++ docs/README.docker.md | 560 ++++++++++++++++++ .../examples/example.camply | 3 + docs/examples/example_search.yml | 13 + pyproject.toml | 3 +- setup.py | 3 +- 20 files changed, 934 insertions(+), 57 deletions(-) create mode 100755 camply/notifications/pushbullet.py create mode 100644 camply/utils/yaml_utils.py create mode 100644 docs/README.docker.md rename example.camply => docs/examples/example.camply (88%) create mode 100644 docs/examples/example_search.yml diff --git a/.github/workflows/camply-publishing.yml b/.github/workflows/camply-publishing.yml index 9dba57cb..dfbecab5 100644 --- a/.github/workflows/camply-publishing.yml +++ b/.github/workflows/camply-publishing.yml @@ -46,4 +46,4 @@ jobs: poetry update cd ${GITHUB_WORKSPACE} && poetry install cd ${GITHUB_WORKSPACE} && poetry build - cd ${GITHUB_WORKSPACE} && poetry publish --username ${PYPI_USERNAME} --password ${PYPI_PASSWORD} \ No newline at end of file + cd ${GITHUB_WORKSPACE} && poetry publish --username ${{ secrets.PYPI_USERNAME }} --password ${{ secrets.PYPI_PASSWORD }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7f938f..fb3394f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ versioning. ## [Unreleased] +## [0.1.4] - 2021-06-01 + +### Added + +- Pushbullet Notifications +- YAML Search Configuration Files + +### Fixed + +- PyPi Publishing CI on Release + ## [0.1.3] - 2021-05-25 ### Added @@ -49,6 +60,8 @@ versioning. [unreleased]: https://github.com/juftin/camply/compare/main...integration +[0.1.4]: https://github.com/juftin/camply/compare/v0.1.3...v0.1.4 + [0.1.3]: https://github.com/juftin/camply/compare/v0.1.2...v0.1.3 [0.1.2]: https://github.com/juftin/camply/compare/v0.1.1...v0.1.2 diff --git a/Dockerfile b/Dockerfile index 701212a0..976b3381 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.8-slim MAINTAINER Justin Flannery -LABEL version="0.1.3" +LABEL version="0.1.4" LABEL description="camply, the campsite finder" COPY . /tmp/camply diff --git a/README.md b/README.md index 71f22a70..98347abe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@
+ camply +
`camply`, the campsite finder ⛺️, is a tool to help you book a campground online. Finding @@ -37,6 +39,7 @@ ___________ + [Look for Weekend Campsite Availabilities](#look-for-weekend-campsite-availabilities) + [Look for a Campsite Inside of Yellowstone](#look-for-a-campsite-inside-of-yellowstone) + [Look for a Campsite Across Multiple Recreation areas](#look-for-a-campsite-across-multiple-recreation-areas) + + [Using a YML Configuration file to search for campsites](#using-a-yml-configuration-file-to-search-for-campsites) + [Search for Recreation Areas by Query String](#search-for-recreation-areas-by-query-string) + [Look for Specific Campgrounds Within a Recreation Area](#look-for-specific-campgrounds-within-a-recreation-area) + [Look for Specific Campgrounds by Query String](#look-for-specific-campgrounds-by-query-string) @@ -115,7 +118,8 @@ Search for a campsite within camply. Campsites are returned based on the search Campsites contain properties like booking date, site type (tent, RV, cabin, etc), capacity, price, and a link to make the booking. Required parameters include `--start-date`, `--end-date`, `--rec-area` / `--campground`. Constant searching functionality can be enabled with -`--continuous` and notifications via Email and Pushover can be enabled using `--notifications`. +`--continuous` and notifications via Email, Pushover, and Pushbullet can be enabled using +`--notifications`. #### Arguments: @@ -140,16 +144,20 @@ and a link to make the booking. Required parameters include `--start-date`, `--e 10, cannot be less than 5. [**_example_](#look-for-weekend-campsite-availabilities) * `--notifications`: `NOTIFICATIONS` + If `--continuous` is activated, types of notifications to receive. Options available are - `email`, `pushover`, or `silent`. Defaults to `silent` - which just logs messages to - console. [**_example_](#continuously-searching-for-a-campsite) + `email`, `pushover`, `pushbullet`, or `silent`. Defaults to `silent` - which just logs + messages to console. [**_example_](#continuously-searching-for-a-campsite) * `--notify-first-try` + If `--continuous` is activated, whether to send all non-silent notifications if more than 5 - matching campsites are found on the first try. Defaults to false which only sends the - first 5. [**_example_](#continuously-searching-for-a-campsite) + matching campsites are found on the first try. Defaults to false which only sends the first + 5. [**_example_](#continuously-searching-for-a-campsite) * `--search-forever` + If `--continuous` is activated, this method continues to search after the first availability has been found. The one caveat is that it will never notify about the same identical campsite for the same booking date. [**_example_](#continue-looking-after-the-first-match-is-found) +* `--yml-config` + + Rather than provide arguments to the command line utility, instead pass a file path to a YAML + configuration file. See the documentation for more information on how to structure your + configuration file. [**_example_](#using-a-yml-configuration-file-to-search-for-campsites) ```text camply campsites \ @@ -204,11 +212,12 @@ camply campgrounds --search "Fire Tower Lookout" --state CA Set up `camply` configuration file with an interactive console In order to send notifications through `camply` you must set up some authorization values. Whether -you need to set up pushover notifications (push notifications on your phone, your pushover account -can be set up at https://pushover.net) or Email messages, everything can be done through the -`configure` command. The end result is a file called [`.camply`](example.camply) in your home -folder. See the [Running in Docker](#running-in-docker) section to see how you can use environment -variables instead of a config file. +you need to set up [Pushover notifications](https://pushover.net) +, [PushBullet](https://www.pushbullet.com/#settings/account) or Email messages, everything can be +done through the `configure` command. The end result is a file called +[`.camply`](docs/examples/example.camply) in your home folder. See +the [Running in Docker](#running-in-docker) section to see how you can use environment variables +instead of a config file. ```text camply configure @@ -253,7 +262,8 @@ camply campsites \ Sometimes you want to look for campgrounds until an eventual match is found. The below snippet will search for matching campsites until it finds a match. It also sends a notification via `pushover` -once matches are found. Alternate notification methods are `email` and `silent` (default). +once matches are found. Alternate notification methods are `email`, `pushbullet`, and `silent` ( +default). __Important Note__: When `camply` is told to run `--continuous` with non-silent notifications set up and it finds more than 5 matching campsites on the first try, it will only send notifications for @@ -335,6 +345,31 @@ camply campsites \ --end-date 2021-07-16 ``` +#### Using a YML Configuration file to search for campsites + +Sometimes, using a YAML configuration file is easier to manage all of your search options. See the +below [YML example file](docs/examples/example_search.yml) and corresponding camply command: + +```yaml +provider: RecreationDotGov # RecreationDotGov IF NOT PROVIDED +recreation_area: # (LIST OR SINGLE ENTRY) + - 2991 # Yosemite National Park, CA (All Campgrounds) + - 1074 # Sierra National Forest, CA (All Campgrounds) +campgrounds: null # ENTIRE FIELD CAN BE OMITTED IF NOT USED. # (LIST OR SINGLE ENTRY) +start_date: 2021-09-12 # YYYY-MM-DD +end_date: 2021-09-12 # YYYY-MM-DD +weekends: False # FALSE BY DEFAULT +continuous: True # DEFAULTS TO TRUE +polling_interval: 5 # DEFAULTS TO 10 , CAN'T BE LESS THAN 5 +notifications: email # (silent, email, pushover, pushbullet), DEFAULTS TO `silent` +search_forever: True # FALSE BY DEFAULT +notify_first_try: False # FALSE BY DEFAULT +``` + +```text +camply campsites --yml-config example_search.yml +``` + #### Search for Recreation Areas by Query String Just need to find what your local Recreation Area ID number is? This simple command allows you to @@ -457,12 +492,12 @@ camping_finder.get_matching_campsites(log=True, verbose=True, ## Running in Docker -Here's an example of a detached container searching in the background (notice the `--rm` flag, the -container will disappear after `camply` exits). +Here's an example of a detached container searching in the background (notice the `-d` flag, the +container will run detached). ```text -docker run -d --rm \ - --name camply \ +docker run -d \ + --name camply-detached-example \ --env PUSHOVER_PUSH_TOKEN=${PUSHOVER_PUSH_TOKEN} \ --env PUSHOVER_PUSH_USER=${PUSHOVER_PUSH_USER} \ --env TZ="America/Denver" \ @@ -496,11 +531,11 @@ The docker image accepts the following environment variables: logging, defaults to UTC) Alternatively, if you have already run `camply configure` locally, you can share -your [`.camply`](example.camply) file inside the docker container. +your [`.camply`](docs/examples/example.camply) file inside the docker container. ```text -docker run -d --rm \ - --name camply \ +docker run \ + --name camply-file-share-example \ --env TZ="America/Denver" \ --volume ${HOME}/.camply:/home/camply/.camply \ juftin/camply \ @@ -512,6 +547,22 @@ docker run -d --rm \ --notifications email ``` +To manage multiple searches (with different notification preferences) I like to use YML +configuration files: + +```text +docker run -d \ + --name camply-email-example \ + --env TZ="America/Denver" \ + --env EMAIL_TO_ADDRESS=${EMAIL_TO_ADDRESS} \ + --env EMAIL_USERNAME=${EMAIL_USERNAME} \ + --env EMAIL_PASSWORD=${EMAIL_PASSWORD} \ + --volume example_search.yml:/home/camply/example_search.yml \ + juftin/camply:latest \ + camply campsites \ + --yml-config /home/camply/example_search.yml +``` + ## Dependencies `camply` is compatible with any Python version >= `3.6`. Currently, there are four required diff --git a/camply/__init__.py b/camply/__init__.py index 9f10ab9c..5e767783 100644 --- a/camply/__init__.py +++ b/camply/__init__.py @@ -6,4 +6,4 @@ camply __init__ file """ -__version__ = "0.1.3" +__version__ = "0.1.4" diff --git a/camply/config/__init__.py b/camply/config/__init__.py index 1d079e7b..cf56d37a 100644 --- a/camply/config/__init__.py +++ b/camply/config/__init__.py @@ -10,6 +10,6 @@ from .cli_config import CommandLineConfig from .data_columns import DataColumns from .file_config import FileConfig -from .notification_config import EmailConfig, PushoverConfig +from .notification_config import EmailConfig, PushbulletConfig, PushoverConfig from .search_config import SearchConfig from .yellowstone_config import YellowstoneConfig diff --git a/camply/config/api_config.py b/camply/config/api_config.py index 41718d77..c6336bb3 100644 --- a/camply/config/api_config.py +++ b/camply/config/api_config.py @@ -13,7 +13,7 @@ from camply.config.file_config import FileConfig -load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=True) +load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=False) USER_AGENTS: List[dict] = [ {"User-Agent": ("Mozilla/5.0 (X11; Linux x86_64; rv:10.0) " diff --git a/camply/config/cli_config.py b/camply/config/cli_config.py index b96b902e..d01ef164 100644 --- a/camply/config/cli_config.py +++ b/camply/config/cli_config.py @@ -15,7 +15,7 @@ from camply.config.file_config import FileConfig from camply.config.search_config import SearchConfig -load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=True) +load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=False) class CommandLineActions: @@ -84,8 +84,8 @@ class CommandLineArguments: NOTIFICATIONS_DEFAULT: str = "silent" NOTIFICATIONS_HELP: str = ("If --continuous is activated, types of notifications to receive. " "Options available are 'email', " - "'pushover', or 'silent'. Defaults to 'silent' - which just logs " - "messages to console.") + "'pushover', 'pushbullet', or 'silent'. Defaults to 'silent' - " + "which just logs messages to console.") NOTIFY_FIRST_TRY_ARGUMENT: str = "--notify-first-try" NOTIFY_FIRST_TRY_DESTINATION: str = "notify_first_try" @@ -101,6 +101,12 @@ class CommandLineArguments: "that it will never notify about the same identical campsite for " "the same booking date.") + YAML_SEARCH_ARGUMENT: str = "--yml-config" + YAML_SEARCH_DESTINATION: str = "yml_config" + YAML_SEARCH_HELP: str = ("Rather than provide arguments to the command line utility, instead " + "pass a file path to a YAML configuration file. See the documentation " + "for more information on how to structure your configuration file.") + class CommandLineValidation: """ @@ -168,8 +174,8 @@ class CommandLineConfig(CommandLineActions, CommandLineArguments, CommandLineVal "Required parameters include `--start-date`, " "`--end-date`, `--rec-area` / `--campground`. " "Constant searching functionality can be enabled with " - " `--continuous` and notifications via Email and " - "Pushover can be enabled using `--notifications`.") + " `--continuous` and notifications " + "can be enabled using `--notifications`.") COMMAND_CONFIGURE: str = "configure" COMMAND_CONFIGURE_HELP: str = "Set up camply configuration file with an interactive console" diff --git a/camply/config/file_config.py b/camply/config/file_config.py index 1cc8c22a..54603202 100644 --- a/camply/config/file_config.py +++ b/camply/config/file_config.py @@ -27,6 +27,7 @@ class FileConfig: DOT_CAMPLY_FIELDS: List[str] = OrderedDict( PUSHOVER_PUSH_TOKEN=dict(default="", notes="Enables Pushover Notifications"), PUSHOVER_PUSH_USER=dict(default="", notes="Enables Pushover Notifications"), + PUSHBULLET_API_TOKEN=dict(default="", notes="Enables Pushbullet Notifications"), EMAIL_TO_ADDRESS=dict(default="", notes="Email Notifications will be sent here"), EMAIL_USERNAME=dict(default="", notes="Email Authorization Login Username"), EMAIL_PASSWORD=dict(default="", notes="Email Authorization Login Password"), diff --git a/camply/config/notification_config.py b/camply/config/notification_config.py index 1ff783c9..15971303 100644 --- a/camply/config/notification_config.py +++ b/camply/config/notification_config.py @@ -15,7 +15,7 @@ from camply.config.file_config import FileConfig logger = logging.getLogger(__name__) -load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=True) +load_dotenv(FileConfig.DOT_CAMPLY_FILE, override=False) class PushoverConfig: @@ -58,3 +58,17 @@ class EmailConfig: f"EMAIL_FROM_ADDRESS (default: {DEFAULT_FROM_ADDRESS})", f'EMAIL_SUBJECT_LINE (default: "{DEFAULT_SUBJECT_LINE}")', f"EMAIL_SMTP_PORT (default: {DEFAULT_SMTP_PORT})"] + + +class PushbulletConfig: + """ + Pushbullet Notification Config Class + """ + + PUSHBULLET_API_ENDPOINT: str = "https://api.pushbullet.com/v2/pushes" + API_HEADERS: dict = {"Content-Type": "application/json"} + + try: + API_TOKEN: str = environ["PUSHBULLET_API_TOKEN"] + except KeyError: + API_TOKEN = None diff --git a/camply/notifications/__init__.py b/camply/notifications/__init__.py index db290526..0ea6a16f 100644 --- a/camply/notifications/__init__.py +++ b/camply/notifications/__init__.py @@ -7,11 +7,13 @@ """ from .email_notifications import EmailNotifications +from .pushbullet import PushbulletNotifications from .pushover import PushoverNotifications from .silent_notifications import SilentNotifications CAMPSITE_NOTIFICATIONS: dict = { "pushover": PushoverNotifications, "email": EmailNotifications, - "silent": SilentNotifications + "silent": SilentNotifications, + "pushbullet": PushbulletNotifications } diff --git a/camply/notifications/pushbullet.py b/camply/notifications/pushbullet.py new file mode 100755 index 00000000..e948390b --- /dev/null +++ b/camply/notifications/pushbullet.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +# Author:: Justin Flannery (mailto:juftin@juftin.com) + +""" +Push Notifications via Pushbullet +""" + +from abc import ABC +from datetime import datetime +import logging +from typing import List, Optional + +import requests + +from camply.config import FileConfig, PushbulletConfig +from .base_notifications import BaseNotifications +from ..containers import AvailableCampsite + +logger = logging.getLogger(__name__) + + +class PushbulletNotifications(BaseNotifications, ABC): + """ + Push Notifications via PushBullet + """ + + def __init__(self): + if any([PushbulletConfig.API_TOKEN is None, + PushbulletConfig.API_TOKEN == ""]): + warning_message = ("Pushbullet is not configured properly. To send Pushbullet messages " + "make sure to run `camply configure` or set the " + "proper environment variable: `PUSHBULLET_API_TOKEN`.") + logger.critical(FileConfig.DOT_CAMPLY_FILE) + logger.error(warning_message) + raise EnvironmentError(warning_message) + + def __repr__(self): + return "" + + @staticmethod + def send_message(message: str, **kwargs) -> Optional[requests.Response]: + """ + Send a message via PushBullet - if environment variables are configured +` + Parameters + ---------- + message: str + + Returns + ------- + Response + """ + pushbullet_headers = PushbulletConfig.API_HEADERS.copy() + pushbullet_headers.update({"Access-Token": PushbulletConfig.API_TOKEN}) + message_type = kwargs.pop("type", "note") + message_title = kwargs.pop("title", "Camply Notification") + message_json = dict(type=message_type, title=message_title, body=message, **kwargs) + logger.debug(message_json) + response = requests.post(url=PushbulletConfig.PUSHBULLET_API_ENDPOINT, + headers=pushbullet_headers, + json=message_json) + if response.status_code != 200: + logger.warning("Notifications weren't able to be sent to Pushbullet. " + "Your configuration might be incorrect.") + logger.debug(response.text) + return response + + @staticmethod + def send_campsites(campsites: List[AvailableCampsite], **kwargs): + """ + Send a message with a campsite object + + Parameters + ---------- + campsites: AvailableCampsite + """ + for campsite in campsites: + fields = list() + for key, value in campsite._asdict().items(): + if key == "booking_url": + key = "Booking Link" + elif key == "booking_date": + value: datetime = value.strftime("%Y-%m-%d") + formatted_key = key.replace("_", " ").title() + fields.append(f"{formatted_key}: {value}") + composed_message = "\n".join(fields) + message_title = (f"{campsite.recreation_area} | {campsite.facility_name} | " + f"{campsite.booking_date.strftime('%Y-%m-%d')}") + PushbulletNotifications.send_message(message=composed_message, title=message_title, + type="note") diff --git a/camply/utils/camply_cli.py b/camply/utils/camply_cli.py index 80e73a37..c538f204 100644 --- a/camply/utils/camply_cli.py +++ b/camply/utils/camply_cli.py @@ -15,8 +15,7 @@ from camply.containers import SearchWindow from camply.providers import RecreationDotGov from camply.search import CAMPSITE_SEARCH_PROVIDER -from camply.search.base_search import SearchError -from camply.utils import configure_camply +from camply.utils import configure_camply, yaml_utils from camply.utils import log_camply logging.Logger.camply = log_camply @@ -207,6 +206,12 @@ def assemble_cli_arguments(self) -> None: required=False, default=False, help=CommandLineConfig.SEARCH_FOREVER_HELP) + self.campsites.add_argument(CommandLineConfig.YAML_SEARCH_ARGUMENT, + action=CommandLineConfig.STORE, + dest=CommandLineConfig.YAML_SEARCH_DESTINATION, + required=False, + default=None, + help=CommandLineConfig.YAML_SEARCH_HELP) self.arguments_compiled = True def parse_arguments(self) -> Namespace: @@ -261,7 +266,8 @@ def _validate_campsites(self) -> Tuple[Optional[str], Optional[ArgumentParser]]: error_message = help_parser = None if self.cli_arguments.provider == CommandLineConfig.RECREATION_DOT_GOV and all( [self.cli_arguments.recreation_area_id is None, - len(self.cli_arguments.campground_id) == 0]): + len(self.cli_arguments.campground_id) == 0, + self.cli_arguments.yml_config is None]): error_message = CommandLineConfig.ERROR_MESSAGE_REC_DOT_GOV help_parser = self.campsites mandatory_parameters = [self.cli_arguments.start_date, @@ -269,7 +275,7 @@ def _validate_campsites(self) -> Tuple[Optional[str], Optional[ArgumentParser]]: mandatory_string_parameters = [CommandLineConfig.START_DATE_ARGUMENT, CommandLineConfig.END_DATE_ARGUMENT] for field in mandatory_parameters: - if field is None: + if field is None and self.cli_arguments.yml_config is None: error_message = (f"{CommandLineConfig.ERROR_MESSAGE_CAMPSITES}: " f"{', '.join(mandatory_string_parameters)}") help_parser = self.campsites @@ -367,26 +373,29 @@ def execute_campsites(self) -> None: ------- None """ - search_window = SearchWindow( - start_date=datetime.strptime(self.cli_arguments.start_date, "%Y-%m-%d"), - end_date=datetime.strptime(self.cli_arguments.end_date, "%Y-%m-%d")) - provider_class = {key.lower(): value for - key, value in CAMPSITE_SEARCH_PROVIDER.items()}[ - self.cli_arguments.provider.lower()] - camping_finder = provider_class(search_window=search_window, - recreation_area=self.cli_arguments.recreation_area_id, - campgrounds=self.cli_arguments.campground_id, - weekends_only=self.cli_arguments.weekends) - try: - camping_finder.get_matching_campsites( - log=True, verbose=True, - continuous=self.cli_arguments.continuous, - polling_interval=float(self.cli_arguments.polling_interval), - notify_first_try=self.cli_arguments.notify_first_try, - notification_provider=self.cli_arguments.notifications, - search_forever=self.cli_arguments.search_forever) - except SearchError: - exit(1) + if self.cli_arguments.yml_config is not None: + provider, provider_kwargs, search_kwargs = yaml_utils.yaml_file_to_arguments( + file_path=self.cli_arguments.yml_config) + else: + provider = self.cli_arguments.provider.lower() + search_window = SearchWindow( + start_date=datetime.strptime(self.cli_arguments.start_date, "%Y-%m-%d"), + end_date=datetime.strptime(self.cli_arguments.end_date, "%Y-%m-%d")) + provider_kwargs = dict(search_window=search_window, + recreation_area=self.cli_arguments.recreation_area_id, + campgrounds=self.cli_arguments.campground_id, + weekends_only=self.cli_arguments.weekends) + search_kwargs = dict(log=True, verbose=True, + continuous=self.cli_arguments.continuous, + polling_interval=float(self.cli_arguments.polling_interval), + notify_first_try=self.cli_arguments.notify_first_try, + notification_provider=self.cli_arguments.notifications, + search_forever=self.cli_arguments.search_forever) + + provider_class = {key.lower(): value for key, value in CAMPSITE_SEARCH_PROVIDER.items()}[ + provider.lower()] + camping_finder = provider_class(**provider_kwargs) + camping_finder.get_matching_campsites(**search_kwargs) def run_cli(self) -> None: """ diff --git a/camply/utils/configure_camply.py b/camply/utils/configure_camply.py index 70a23454..26ce42d7 100644 --- a/camply/utils/configure_camply.py +++ b/camply/utils/configure_camply.py @@ -133,7 +133,7 @@ def generate_dot_camply_file(): sleep(1.5) if isfile(FileConfig.DOT_CAMPLY_FILE): logger.warning(f".camply file already exists on this machine: {FileConfig.DOT_CAMPLY_FILE}") - overwrite = double_check("Would you like to overwrite your `.campy` configuration file?") + overwrite = double_check("Would you like to overwrite your `.camply` configuration file?") if overwrite is False: exit(0) config = generate_configuration() diff --git a/camply/utils/yaml_utils.py b/camply/utils/yaml_utils.py new file mode 100644 index 00000000..f7162dd4 --- /dev/null +++ b/camply/utils/yaml_utils.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +# Author:: Justin Flannery (mailto:juftin@juftin.com) + +""" +YAML Utilities for Camply +""" + +from datetime import datetime +import logging +from os import getenv +from re import compile +from typing import Dict, Tuple + +from yaml import load, SafeLoader + +from camply.config import SearchConfig +from camply.containers import SearchWindow + +logger = logging.getLogger(__name__) + + +def read_yml(path: str = None): + """ + Load a yaml configuration file_path (path) or data object (data) + and resolve any environment variables. The environment + variables must be in this format to be parsed: ${VAR_NAME}. + Parameters + ---------- + path: str + File Path of YAML Object to Read + + Examples + ---------- + database: + host: ${HOST} + port: ${PORT} + ${KEY}: ${VALUE} + app: + log_path: "/var/${LOG_PATH}" + something_else: "${AWESOME_ENV_VAR}/var/${A_SECOND_AWESOME_VAR}" + """ + pattern = compile(r".*?\${(\w+)}.*?") + + safe_loader = SafeLoader + safe_loader.add_implicit_resolver(tag=None, regexp=pattern, first=None) + + def env_var_constructor(safe_loader: object, node: object): + """ + Extracts the environment variable from the node's value + :param yaml.Loader safe_loader: the yaml loader + :param node: the current node in the yaml + :return: the parsed string that contains the value of the environment + variable + """ + value = safe_loader.construct_scalar(node=node) + match = pattern.findall(string=value) + if match: + full_value = value + for g in match: + full_value = full_value.replace( + "${{{key}}}".format(key=g), getenv(key=g, default=g)) + return full_value + return value + + safe_loader.add_constructor(tag=None, constructor=env_var_constructor) + with open(path) as conf_data: + return load(stream=conf_data, Loader=safe_loader) + + +def yaml_file_to_arguments(file_path: str) -> Tuple[str, Dict[str, object], Dict[str, object]]: + """ + Convert YAML File into A Dictionary to be used as **kwargs + + Parameters + ---------- + file_path: str + File Path to YAML + + Returns + ------- + provider, provider_kwargs, search_kwargs: Tuple[str, Dict[str, object], Dict[str, object]] + Tuple containing provider string, provider **kwargs, and search **kwargs + """ + yaml_search = read_yml(path=file_path) + provider = yaml_search.get("provider", "RecreationDotGov") + start_date = datetime.strptime(str(yaml_search["start_date"]), "%Y-%m-%d") + end_date = datetime.strptime(str(yaml_search["end_date"]), "%Y-%m-%d") + recreation_area = yaml_search.get("recreation_area", None) + campgrounds = yaml_search.get("campgrounds", None) + weekends_only = yaml_search.get("weekends", False) + continuous = yaml_search.get("continuous", True) + polling_interval = yaml_search.get("polling_interval", + SearchConfig.RECOMMENDED_POLLING_INTERVAL) + notify_first_try = yaml_search.get("notify_first_try", False) + notification_provider = yaml_search.get("notifications", "silent") + search_forever = yaml_search.get("search_forever", False) + + search_window = SearchWindow(start_date=start_date, end_date=end_date) + + provider_kwargs = dict(search_window=search_window, + recreation_area=recreation_area, + campgrounds=campgrounds, + weekends_only=weekends_only) + search_kwargs = dict( + log=True, verbose=True, + continuous=continuous, + polling_interval=polling_interval, + notify_first_try=notify_first_try, + notification_provider=notification_provider, + search_forever=search_forever) + return provider, provider_kwargs, search_kwargs diff --git a/docs/README.docker.md b/docs/README.docker.md new file mode 100644 index 00000000..84ab2324 --- /dev/null +++ b/docs/README.docker.md @@ -0,0 +1,560 @@ +
+ + camply + +
+ +`camply`, the campsite finder ⛺️, is a tool to help you book a campground online. Finding +reservations at sold out campgrounds can be tough. That's where camply comes in. It searches the +APIs of booking services like https://recreation.gov (which indexes thousands of campgrounds across +the USA) to continuously check for cancellations and availabilities to pop up. Once a campsite +becomes available, camply sends you a notification to book your spot! + +___________ +___________ + +[![PyPI](https://img.shields.io/pypi/v/camply?color=blue&label=⛺️camply)](https://github.com/juftin/camply) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/camply)](https://pypi.python.org/pypi/camply/) +[![Docker Image Version](https://img.shields.io/docker/v/juftin/camply?color=blue&label=docker&logo=docker)](https://hub.docker.com/r/juftin/camply) +[![Testing Status](https://github.com/juftin/camply/actions/workflows/pytest.yml/badge.svg?branch=camply)](https://github.com/juftin/camply/actions/workflows/pytest.yml) +[![GitHub License](https://img.shields.io/github/license/juftin/camply?color=blue&label=License)](https://github.com/juftin/camply/blob/main/LICENSE) + +## Installation + +### [PyPI](https://pypi.python.org/pypi/camply/) + +```text +pip install camply +``` + +### [Docker](https://hub.docker.com/r/juftin/camply) + +```text +docker pull juftin/camply +``` + +**_see [Running in Docker](#running-in-docker) below._ + +### Building Locally + +```text +git clone https://github.com/juftin/camply.git +cd camply +python setup.py install +``` + +## Running in Docker + +Here's an example of a detached container searching in the background (notice the `-d` flag, the +container will run detached). + +```text +docker run -d \ + --name camply-detached-example \ + --env PUSHOVER_PUSH_TOKEN=${PUSHOVER_PUSH_TOKEN} \ + --env PUSHOVER_PUSH_USER=${PUSHOVER_PUSH_USER} \ + --env TZ="America/Denver" \ + juftin/camply \ + camply campsites \ + --rec-area 2991 \ + --start-date 2021-08-01 \ + --end-date 2021-08-31 \ + --continuous \ + --notifications pushover +``` + +The docker image accepts the following environment variables: + +- Pushover Notifications + * `PUSHOVER_PUSH_TOKEN` + * `PUSHOVER_PUSH_USER` +- Email Notifications + * `EMAIL_TO_ADDRESS` + * `EMAIL_USERNAME` + * `EMAIL_PASSWORD` + * `EMAIL_FROM_ADDRESS` (defaults to "camply@juftin.com") + * `EMAIL_SUBJECT_LINE` (defaults to "camply Notification") + * `EMAIL_SMTP_SERVER` (defaults to "smtp.gmail.com") + * `EMAIL_SMTP_PORT` (defaults to 465) +- Optional Environment Variables + * `LOG_LEVEL` (sets logging level, defaults to "INFO") + * `RIDB_API_KEY` (Personal API Key + for [Recreation.gov API](https://ridb.recreation.gov/profile)) + * `TZ` ([TZ Database Name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for + logging, defaults to UTC) + +Alternatively, if you have already run `camply configure` locally, you can share +your [`.camply`](docs/examples/example.camply) file inside the docker container. + +```text +docker run \ + --name camply-file-share-example \ + --env TZ="America/Denver" \ + --volume ${HOME}/.camply:/home/camply/.camply \ + juftin/camply \ + camply campsites \ + --provider yellowstone \ + --start-date 2021-07-22 \ + --end-date 2021-07-26 \ + --continuous \ + --notifications email +``` + +To manage multiple searches (with different notification preferences) I like to use YML +configuration files: + +```text +docker run -d \ + --name camply-email-example \ + --env TZ="America/Denver" \ + --env EMAIL_TO_ADDRESS=${EMAIL_TO_ADDRESS} \ + --env EMAIL_USERNAME=${EMAIL_USERNAME} \ + --env EMAIL_PASSWORD=${EMAIL_PASSWORD} \ + --volume example_search.yml:/home/camply/example_search.yml \ + juftin/camply:latest \ + camply campsites \ + --yml-config /home/camply/example_search.yml +``` + +## Command Line Usage + +When installed, `camply`'s command line utility can be invoked with the command, `camply`. The CLI +tool accepts one of four sub-arguments: `campsites`, `recreation-areas`, `campgrounds`, +and `configure`. + +```text +❯ camply --help + +2021-05-17 19:11:59,858 [ CAMPLY]: camply, the campsite finder ⛺️ +usage: camply [-h] [--version] + {campsites,recreation-areas,campgrounds,configure} ... + +Welcome to camply, the campsite finder. Finding reservations at sold out +campgrounds can be tough. That's where camply comes in. It searches the APIs +of booking services like https://recreation.gov (which indexes thousands of +campgrounds across the USA) to continuously check for cancellations and +availabilities to pop up. Once a campsite becomes available, camply sends you +a notification to book your spot! + +positional arguments: + {campsites,recreation-areas,campgrounds,configure} + campsites Find available Campsites using search criteria + recreation-areas Search for Recreation Areas and list them + campgrounds Search for Campgrounds (inside of Recreation Areas) + and list them + configure Set up camply configuration file with an interactive + console + +optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + +visit the camply documentation at https://github.com/juftin/camply + +2021-05-17 19:11:59,863 [ CAMPLY]: Exiting camply 👋 +``` + +### `campsites` + +Search for a campsite within camply. Campsites are returned based on the search criteria provided. +Campsites contain properties like booking date, site type (tent, RV, cabin, etc), capacity, price, +and a link to make the booking. Required parameters include `--start-date`, `--end-date`, +`--rec-area` / `--campground`. Constant searching functionality can be enabled with +`--continuous` and notifications via Email, Pushover, and Pushbullet can be enabled using +`--notifications`. + +#### Arguments: + +* `--rec-area`: `RECREATION_AREA_ID` + + Add Recreation Areas (comprised of campgrounds) by ID. [**_example_](#searching-for-a-campsite) +* `--campground`: `CAMPGROUND_ID` + + Add individual Campgrounds by ID. [**_example_](#searching-for-a-campsite-by-campground-id) +* `--start-date`: `START_DATE` + + `YYYY-MM-DD`: Start of Search window. You will be arriving this day. [**_example_](#searching-for-a-campsite) +* `--end-date`: `END_DATE` + + `YYYY-MM-DD`: End of Search window. You will be leaving the following day. [**_example_](#searching-for-a-campsite) +* `--weekends` + + Only search for weekend bookings (Fri/Sat nights). [**_example_](#look-for-weekend-campsite-availabilities) +* `--provider`: `PROVIDER` + + Camping Search Provider. Options available are 'Yellowstone' and 'RecreationDotGov'. Defaults + to 'RecreationDotGov', not case-sensitive. [**_example_](#look-for-a-campsite-inside-of-yellowstone) +* `--continuous` + + Continuously check for a campsite to become available, and quit once at least one campsite is + found. [**_example_](#continuously-searching-for-a-campsite) +* `--polling-interval`: `POLLING_INTERVAL` + + If `--continuous` is activated, how often to wait in between checks (in minutes). Defaults to + 10, cannot be less than 5. [**_example_](#look-for-weekend-campsite-availabilities) +* `--notifications`: `NOTIFICATIONS` + + If `--continuous` is activated, types of notifications to receive. Options available are + `email`, `pushover`, `pushbullet`, or `silent`. Defaults to `silent` - which just logs + messages to console. [**_example_](#continuously-searching-for-a-campsite) +* `--notify-first-try` + + If `--continuous` is activated, whether to send all non-silent notifications if more than 5 + matching campsites are found on the first try. Defaults to false which only sends the first + 5. [**_example_](#continuously-searching-for-a-campsite) +* `--search-forever` + + If `--continuous` is activated, this method continues to search after the first availability + has been found. The one caveat is that it will never notify about the same identical campsite + for the same booking date. [**_example_](#continue-looking-after-the-first-match-is-found) +* `--yml-config` + + Rather than provide arguments to the command line utility, instead pass a file path to a YAML + configuration file. See the documentation for more information on how to structure your + configuration file. [**_example_](#using-a-yml-configuration-file-to-search-for-campsites) + +```text +camply campsites \ + --rec-area 2725 \ + --start-date 2021-07-10 \ + --end-date 2021-07-17 +``` + +### `recreation-areas` + +Search for Recreation Areas and their IDs. Recreation Areas are places like National Parks and +National Forests that can contain one or many campgrounds. + +#### Arguments: + +* `--search` `SEARCH` + + Search for Campgrounds or Recreation Areas by search string. +* `--state` `STATE` + + Filter by US state code. + +```text +camply recreation-areas --search "Yosemite National Park" +``` + +**_see the [examples](#search-for-recreation-areas-by-query-string) for more information_ + +### `campgrounds` + +Search for Campgrounds and their IDs. Campgrounds are facilities inside of Recreation Areas that +contain campsites. Most 'campgrounds' are areas made up of multiple campsites, others are facilities +like fire towers or cabins that might only contain a single 'campsite' to book. + +#### Arguments: + +* `--search` `SEARCH` + + Search for Campgrounds or Recreation Areas by search string. +* `--state` `STATE` + + Filter by US state code. +* `--rec-area`: `RECREATION_AREA_ID` + + Add Recreation Areas (comprised of campgrounds) by ID. +* `--campground`: `CAMPGROUND_ID` + + Add individual Campgrounds by ID. + +```text +camply campgrounds --search "Fire Tower Lookout" --state CA +``` + +**_see the [examples](#look-for-specific-campgrounds-by-query-string) for more information_ + +### `configure` + +Set up `camply` configuration file with an interactive console + +In order to send notifications through `camply` you must set up some authorization values. Whether +you need to set up [Pushover notifications](https://pushover.net) +, [PushBullet](https://www.pushbullet.com/#settings/account) or Email messages, everything can be +done through the `configure` command. The end result is a file called +[`.camply`](docs/examples/example.camply) in your home folder. See +the [Running in Docker](#running-in-docker) section to see how you can use environment variables +instead of a config file. + +```text +camply configure +``` + +### Examples + +Read through the examples below to get a better understanding of `camply`, its features, and the +functionality of the different arguments provided to the CLI. + +#### Searching for a Campsite + +The below search looks for campsites inside of Recreation Area ID #2725 (Glacier National Park) +between 2021-07-10 and 2021-07-17. The search will be performed once and any results will be logged +to the console. camply searches for campsites inside of search windows in increments of one night. +`--start-date` and `--end-date` define the bounds of the search window, you will be leaving the day +after `--end-date`. + +```text +camply campsites \ + --rec-area 2725 \ + --start-date 2021-07-10 \ + --end-date 2021-07-17 +``` + +#### Searching for a Campsite by Campground ID + +The below search looks for across three campgrounds (all inside Glacier National Park) +between 2021-07-10 and 2021-07-17. Multiple Campgrounds (and Recreation Areas too) can be found by +supplying the arguments more than once. + +```text +camply campsites \ + --campground 232493 \ + --campground 251869 \ + --campground 232492 \ + --start-date 2021-07-10 \ + --end-date 2021-07-17 +``` + +#### Continuously Searching for A Campsite + +Sometimes you want to look for campgrounds until an eventual match is found. The below snippet will +search for matching campsites until it finds a match. It also sends a notification via `pushover` +once matches are found. Alternate notification methods are `email`, `pushbullet`, and `silent` ( +default). + +__Important Note__: When `camply` is told to run `--continuous` with non-silent notifications set up +and it finds more than 5 matching campsites on the first try, it will only send notifications for +the first 5 campsites. This is to prevent thousands of campsites flooding your notifications. It's +always encouraged to perform an initial online search before setting up a `camply` search. To bypass +this behavior and send all notifications, pass the `--notify-first-try` argument. + +```text +camply campsites \ + --rec-area 2725 \ + --start-date 2021-07-12 \ + --end-date 2021-07-12 \ + --continuous \ + --notifications pushover \ + --notify-first-try +``` + +#### Continue Looking After The First Match Is Found + +Sometimes you want to search for all possible matches up until your arrival date. No problem. Add +the `--search-forever` and `camply` won't stop sending notifications after the first match is found. +One important note, `camply` will save and store all previous notifications when `--search-forever` +is enabled, so it won't notify you about the exact same campsite availability twice. This can be +problematic when certain campsites become available more than once. + +```text +camply campsites \ + --rec-area 2725 \ + --start-date 2021-07-01 \ + --end-date 2021-07-31 \ + --continuous \ + --notifications pushover \ + --search-forever +``` + +#### Look for Weekend Campsite Availabilities + +This below search looks across larger periods of time, but only if a campground is available to book +on a Friday or Saturday night (`--weekends`). It also uses the `--polling-interval` argument which +checks every 5 minutes instead of the default 10 minutes. + +```text +camply campsites \ + --rec-area 2991 \ + --start-date 2021-05-01 \ + --end-date 2021-07-31 \ + --weekends \ + --continuous \ + --notifications email \ + --polling-interval 5 +``` + +#### Look for a Campsite Inside of Yellowstone + +Yellowstone doesn't use https://recreation.gov to manage its campgrounds, instead it uses its own +proprietary system. In order to search the Yellowstone API for campsites, make sure to pass +the `--provider "yellowstone"` argument. This flag disables `--rec-area` and `--campground` +arguments. + +```text +camply campsites \ + --provider yellowstone \ + --start-date 2021-07-09 \ + --end-date 2021-07-16 \ + --continuous +``` + +#### Look for a Campsite Across Multiple Recreation areas + +You don't have to confine your search to a single Recreation or Campground ID. Adding multiple +arguments to the command line will search across multiple IDs. Keep in mind that any `--campground` +arguments will overwrite all `--rec-area` arguments. + +```text +camply campsites \ + --rec-area 2991 \ + --rec-area 1074 \ + --start-date 2021-07-09 \ + --end-date 2021-07-16 +``` + +#### Using a YML Configuration file to search for campsites + +Sometimes, using a YAML configuration file is easier to manage all of your search options. See the +below [YML example file](docs/examples/example_search.yml) and corresponding camply command: + +```yaml +provider: RecreationDotGov # RecreationDotGov IF NOT PROVIDED +recreation_area: # (LIST OR SINGLE ENTRY) + - 2991 # Yosemite National Park, CA (All Campgrounds) + - 1074 # Sierra National Forest, CA (All Campgrounds) +campgrounds: null # ENTIRE FIELD CAN BE OMITTED IF NOT USED. # (LIST OR SINGLE ENTRY) +start_date: 2021-09-12 # YYYY-MM-DD +end_date: 2021-09-12 # YYYY-MM-DD +weekends: False # FALSE BY DEFAULT +continuous: True # DEFAULTS TO TRUE +polling_interval: 5 # DEFAULTS TO 10 , CAN'T BE LESS THAN 5 +notifications: email # (silent, email, pushover, pushbullet), DEFAULTS TO `silent` +search_forever: True # FALSE BY DEFAULT +notify_first_try: False # FALSE BY DEFAULT +``` + +```text +camply campsites --yml-config example_search.yml +``` + +#### Search for Recreation Areas by Query String + +Just need to find what your local Recreation Area ID number is? This simple command allows you to +search and list recreation areas. It accepts `--search` and `--state` arguments. + +```text +camply recreation-areas --search "Yosemite National Park" +``` + +#### Look for Specific Campgrounds Within a Recreation Area + +Need to get even more specific and search for a particular campground? This search lists campgrounds +attached to a recreation area id `--rec-area`. It also accepts `--search` and `--state` +arguments. + +```text +camply campgrounds --rec-area 2991 +``` + +#### Look for Specific Campgrounds by Query String + +The below search looks for Fire Lookout Towers to stay in inside of California. + +```text +camply campgrounds --search "Fire Tower Lookout" --state CA +``` + +## Finding Recreation Areas IDs and Campground IDs To Search Without Using the Command Line + +You can uncover campground and recreation area IDs just by using the https://recreation.gov search +functionality. Use the below example for a campground within Glacier National Park. + +First, perform your search on https://recreation.gov. + +
+recreation_dot_gov search +
+ +The above search will take you to a URL like this: +https://www.recreation.gov/search?q=Glacier%20National%20Park&entity_id=2725&entity_type=recarea. +Taking a closer look at the URL components you can see that Glacier National Park has the Recreation +Area ID #2725. + +Searching deeper into campgrounds inside of Glacier National Park you might find Fish Creek +Campground at a URL like https://www.recreation.gov/camping/campgrounds/232493. Here, we can see +that this campground has a Campground ID of #232493. + +## Object-Oriented Usage (Python) + +### Search for a Recreation.gov Campsite + +```python +from datetime import datetime +import logging +from typing import List + +from camply.containers import AvailableCampsite, SearchWindow +from camply.search import SearchRecreationDotGov + +logging.basicConfig(format="%(asctime)s [%(levelname)8s]: %(message)s", + level=logging.INFO) + +month_of_june = SearchWindow(start_date=datetime(year=2021, month=6, day=1), + end_date=datetime(year=2021, month=6, day=30)) +camping_finder = SearchRecreationDotGov(search_window=month_of_june, + recreation_area=2725, # Glacier Ntl Park + weekends_only=False) +matches: List[AvailableCampsite] = camping_finder.get_matching_campsites(log=True, verbose=True, + continuous=False) +``` + +The above script returns a list of any matching `AvailableCampsite` namedtuple objects: + +```python +[ + AvailableCampsite(campsite_id="5391", + booking_date=datetime.datetime(2021, 6, 13, 0, 0), + campsite_site_name="B37", + campsite_loop_name="Loop B", + campsite_type="STANDARD NONELECTRIC", + campsite_occupancy=(0, 8), + campsite_use_type="Overnight", + availability_status="Available", + recreation_area="Glacier National Park, MT", + recreation_area_id="2725", + facility_name="Fish Creek Campground", + facility_id="232493", + booking_url="https://www.recreation.gov/camping/campsites/5391") +] +``` + +### Continuously Search for Recreation.gov Campsites + +You'll notice that the `get_matching_campsites` function takes accepts parameter values very similar +to the commandline arguments. + +```python +from datetime import datetime +import logging + +from camply.containers import SearchWindow +from camply.search import SearchRecreationDotGov + +logging.basicConfig(format="%(asctime)s [%(levelname)8s]: %(message)s", + level=logging.INFO) + +month_of_june = SearchWindow(start_date=datetime(year=2021, month=6, day=1), + end_date=datetime(year=2021, month=6, day=30)) +camping_finder = SearchRecreationDotGov(search_window=month_of_june, + recreation_area=[2991, 1074], # Multiple Rec Areas + weekends_only=False) +camping_finder.get_matching_campsites(log=True, verbose=True, + continuous=True, + polling_interval=5, + notification_provider="pushover", + search_forever=True, + notify_first_try=False) +``` + +## Dependencies + +`camply` is compatible with any Python version >= `3.6`. Currently, there are four required +dependencies: + +- [requests](https://docs.python-requests.org/en/master/) + - The `requests` package is used to fetch data from the APIs of Camping Booking Providers. +- [pandas](https://pandas.pydata.org/) + - The `pandas` package is to group and aggregate across large data sets of campsites, + campgrounds, and recreation areas. +- [tenacity](https://tenacity.readthedocs.io/en/latest/) + - The `tenacity` package is used for retrying data searches on the underlying campsite APIs. + This retrying methodology handles exceptions allowing for API downtime and facilitating + exponential backoff. +- [python-dotenv](https://github.com/theskumar/python-dotenv) + - The `python-dotenv` package reads key-value pairs from a `.env` file and can set them as + environment variables - this helps with the `.camply` configuration file. + +___________ +___________ + +
+ +[

juftin logo

](https://github.com/juftin) + diff --git a/example.camply b/docs/examples/example.camply similarity index 88% rename from example.camply rename to docs/examples/example.camply index 8d65a48f..e6ec9f92 100644 --- a/example.camply +++ b/docs/examples/example.camply @@ -5,6 +5,9 @@ PUSHOVER_PUSH_TOKEN="" PUSHOVER_PUSH_USER="" +# REQUIRED TO SEND PUSHBULLET NOTIFICATIONS +PUSHBULLET_API_TOKEN="" + # REQUIRED TO SEND EMAIL NOTIFICATIONS EMAIL_TO_ADDRESS="" EMAIL_USERNAME="" diff --git a/docs/examples/example_search.yml b/docs/examples/example_search.yml new file mode 100644 index 00000000..11f83320 --- /dev/null +++ b/docs/examples/example_search.yml @@ -0,0 +1,13 @@ +provider: RecreationDotGov # RecreationDotGov IF NOT PROVIDED +recreation_area: # (LIST OR SINGLE ENTRY) + - 2991 # Yosemite National Park, CA (All Campgrounds) + - 1074 # Sierra National Forest, CA (All Campgrounds) +campgrounds: null # OVERRIDES RECREATION AREA (LIST OR SINGLE ENTRY) +start_date: 2021-09-12 # YYYY-MM-DD +end_date: 2021-09-12 # YYYY-MM-DD +weekends: False # FALSE BY DEFAULT +continuous: False # DEFAULTS TO TRUE +polling_interval: 5 # DEFAULTS TO 10 , CAN'T BE LESS THAN 5 +notifications: email # (silent, email, pushover, pushbullet), DEFAULTS TO `silent` +search_forever: True # FALSE BY DEFAULT +notify_first_try: False # FALSE BY DEFAULT diff --git a/pyproject.toml b/pyproject.toml index db8bd813..f5aa719d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "camply" -version = "0.1.3" +version = "0.1.4" description = "camply, the campsite finder ⛺️" authors = ["Justin Flannery "] maintainers = ["Justin Flannery "] @@ -19,6 +19,7 @@ pandas = "*" tenacity = "*" python-dotenv = "*" pytz = "*" +PyYAML = "*" [tool.poetry.dev-dependencies] pytest = "*" diff --git a/setup.py b/setup.py index 0090cbfe..bc16c1d0 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,8 @@ package_data = {"": ["*"]} -install_requires = ["pandas", +install_requires = ["PyYAML", + "pandas", "python-dotenv", "pytz", "requests",