Skip to content

Commit

Permalink
Merge pull request #13 from lemeryfertitta/12-document-and-release-au…
Browse files Browse the repository at this point in the history
…rora-syncdownload

Document and release database command
  • Loading branch information
lemeryfertitta authored Dec 10, 2023
2 parents 20bd0d6 + 02025a1 commit bf417a3
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 32 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
# BoardLib 🧗‍♀️

Utilities for interacting with (undocumented) climbing board APIs. Currently, the primary use case for this library is to retreive your logbook entries across multiple climbing boards in a unified format. There are also some APIs which could also be used for retrieving climb data, rankings, etc. Contributions are welcome for filling out additional API calls and use cases.
Utilities for interacting with (undocumented) climbing board APIs.

## Installation 🦺

`python3 -m pip install boardlib`

## Usage ⌨️

### Databases 💾

To download the climb database for a given board:

`boardlib database <board_name> <database_path>`

This will download a [sqlite](https://www.sqlite.org/index.html) database file to the given path and synchronize it with the latest available data. The database contains all of the publicly available climb data. Only the synchronization will be attempted if there is already a database at the given path,

NOTE: The Moonboard is not currently supported for the database command. Contributions are welcome.

#### Supported Boards 🛹

All [Aurora Climbing](https://auroraclimbing.com/) based boards (Kilter, Tension, etc.).

### Logbooks 📚

To download your logbook entries for a given board:

`boardlib <board_name> --username=<username> --output=<output_file_name>.csv --grade-type="hueco"`
`boardlib logbook <board_name> --username=<username> --output=<output_file_name>.csv --grade-type="hueco"`

This outputs a CSV file with the following fields:

Expand All @@ -34,7 +50,7 @@ moon2017,40,MOUNTAIN GOAT HARD,2021-07-13,V5,1, False

See `boardlib --help` for a full list of supported board names and feature flags.

## Supported Boards 🛹
#### Supported Boards 🛹

Currently all [Aurora Climbing](https://auroraclimbing.com/) based boards (Kilter, Tension, etc.) and all variations of the [Moonboard](https://moonboard.com/) should be supported.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "boardlib"
version = "0.1.0"
version = "0.2.0"
authors = [{ name = "Luke Emery-Fertitta", email = "[email protected]" }]
description = "Utilities for interacting with climbing board APIs"
readme = "README.md"
Expand Down
96 changes: 68 additions & 28 deletions src/boardlib/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,15 @@
import csv
import getpass
import os
import pathlib
import sys

import boardlib.api.aurora
import boardlib.api.moon
import boardlib.db.aurora


LOGBOOK_FIELDS = (
"board",
"angle",
"name",
"date",
"grade",
"tries",
"is_mirror"
)
LOGBOOK_FIELDS = ("board", "angle", "name", "date", "grade", "tries", "is_mirror")


def logbook_entries(board, username, password, grade_type="font"):
Expand All @@ -42,42 +36,88 @@ def write_entries(output_file, entries, no_headers=False):
writer.writerows(entries)


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
def handle_logbook_command(args):
env_var = f"{args.board.upper()}_PASSWORD"
password = os.environ.get(env_var)
if not password:
password = getpass.getpass("Password: ")
entries = logbook_entries(args.board, args.username, password, args.grade_type)

if args.output:
with open(args.output, "w", encoding="utf-8") as output_file:
write_entries(output_file, entries, args.no_headers)
else:
sys.stdout.reconfigure(encoding="utf-8")
write_entries(sys.stdout, entries, args.no_headers)


def handle_database_command(args):
if not args.database_path.exists():
args.database_path.parent.mkdir(parents=True, exist_ok=True)
print(f"Downloading database to {args.database_path}")
boardlib.db.aurora.download_database(args.board, args.database_path)

print(f"Synchronizing database at {args.database_path}")
row_counts = boardlib.db.aurora.sync_shared_tables(args.board, args.database_path)
for table_name, row_count in row_counts.items():
print(f"Synchronized {row_count} rows in {table_name}")


def add_database_parser(subparsers):
database_parser = subparsers.add_parser(
"database", help="Download and sync the database"
)
database_parser.add_argument(
"board",
help="Board name",
choices=sorted(boardlib.api.aurora.HOST_BASES.keys()),
)
database_parser.add_argument(
"database_path",
help=(
"Path for the database file. "
"If the file does not exist, the database will be downloaded to the given path and synchronized. "
"If it does exist, the database will just be synchronized",
),
type=pathlib.Path,
)
database_parser.set_defaults(func=handle_database_command)


def add_logbook_parser(subparsers):
logbook_parser = subparsers.add_parser(
"logbook", help="Download logbook entries to CSV"
)
logbook_parser.add_argument(
"board",
help="Board name",
choices=sorted(
boardlib.api.moon.BOARD_IDS.keys() | boardlib.api.aurora.HOST_BASES.keys()
),
)
parser.add_argument("-u", "--username", help="Username", required=True)
parser.add_argument("-o", "--output", help="Output file", required=False)
parser.add_argument(
logbook_parser.add_argument("-u", "--username", help="Username", required=True)
logbook_parser.add_argument("-o", "--output", help="Output file", required=False)
logbook_parser.add_argument(
"--no-headers", help="Don't write headers", action="store_true", required=False
)
parser.add_argument(
logbook_parser.add_argument(
"-g",
"--grade-type",
help="Grade type",
choices=("font", "hueco"),
default="font",
required=False,
)
args = parser.parse_args()
logbook_parser.set_defaults(func=handle_logbook_command)

env_var = f"{args.board.upper()}_PASSWORD"
password = os.environ.get(env_var)
if not password:
password = getpass.getpass("Password: ")
entries = logbook_entries(args.board, args.username, password, args.grade_type)

if args.output:
with open(args.output, "w", encoding='utf-8') as output_file:
write_entries(output_file, entries, args.no_headers)
else:
sys.stdout.reconfigure(encoding= 'utf-8')
write_entries(sys.stdout, entries, args.no_headers)
def main():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command", required=True)
add_logbook_parser(subparsers)
add_database_parser(subparsers)
args = parser.parse_args()
args.func(args)


if __name__ == "__main__":
Expand Down
5 changes: 5 additions & 0 deletions src/boardlib/db/aurora.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def sync_shared_tables(board, database):
:param board: The board to sync the database for.
:param database: The sqlite3 database file to sync.
:returns: a mapping of synchronized table names to counts of inserted/updated/deleted rows.
"""
with sqlite3.connect(database) as connection:
result = connection.execute(
Expand All @@ -61,10 +62,14 @@ def sync_shared_tables(board, database):
shared_sync_result = boardlib.api.aurora.shared_sync(
board, tables=boardlib.api.aurora.SHARED_TABLES, shared_syncs=shared_syncs
)
row_counts = {}
for table_name, rows in shared_sync_result["PUT"].items():
ROW_INSERTERS.get(table_name, insert_rows_default)(
connection, table_name, rows
)
row_counts[table_name] = len(rows)

return row_counts


def insert_rows_default(connection, table_name, rows):
Expand Down

0 comments on commit bf417a3

Please sign in to comment.