arccos-api 0.1.4__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arccos_api-0.1.4 → arccos_api-0.2.0}/PKG-INFO +17 -8
- {arccos_api-0.1.4 → arccos_api-0.2.0}/README.md +16 -7
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/__init__.py +3 -1
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/_http.py +49 -8
- arccos_api-0.2.0/arccos/cli/__init__.py +54 -0
- arccos_api-0.2.0/arccos/cli/_helpers.py +81 -0
- arccos_api-0.2.0/arccos/cli/auth.py +55 -0
- arccos_api-0.2.0/arccos/cli/rounds.py +441 -0
- arccos_api-0.2.0/arccos/cli/stats.py +504 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/rounds.py +44 -0
- arccos_api-0.2.0/arccos/types.py +143 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/PKG-INFO +17 -8
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/SOURCES.txt +6 -1
- {arccos_api-0.1.4 → arccos_api-0.2.0}/pyproject.toml +2 -2
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_cli.py +58 -46
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_http.py +120 -1
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_rounds.py +24 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_security.py +1 -1
- arccos_api-0.1.4/arccos/cli.py +0 -1079
- {arccos_api-0.1.4 → arccos_api-0.2.0}/MANIFEST.in +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/__main__.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/auth.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/client.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/exceptions.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/__init__.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/clubs.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/courses.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/handicap.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos/resources/stats.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/dependency_links.txt +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/entry_points.txt +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/requires.txt +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/arccos_api.egg-info/top_level.txt +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/setup.cfg +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_auth.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_client.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_exceptions.py +0 -0
- {arccos_api-0.1.4 → arccos_api-0.2.0}/tests/test_resources.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arccos-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Unofficial Python CLI and client library for the Arccos Golf API
|
|
5
5
|
Author: Paul Frederiksen
|
|
6
6
|
License: MIT
|
|
@@ -76,14 +76,17 @@ arccos clubs # smart club distances
|
|
|
76
76
|
| Command | Description |
|
|
77
77
|
|---------|-------------|
|
|
78
78
|
| `arccos login` | Authenticate and cache credentials to `~/.arccos_creds.json` |
|
|
79
|
-
| `arccos rounds` | List recent rounds with date, score,
|
|
79
|
+
| `arccos rounds` | List recent rounds with date, score, +/-, course name |
|
|
80
80
|
| `arccos round <id>` | Hole-by-hole detail (score, putts, FIR, GIR) with totals |
|
|
81
81
|
| `arccos handicap` | Handicap breakdown by category (overall, driving, approach, etc.) |
|
|
82
82
|
| `arccos clubs` | Smart club distances with make/model, range, and shot count |
|
|
83
|
+
| `arccos bests` | All-time personal bests (lowest score, longest drive, etc.) |
|
|
84
|
+
| `arccos overview` | Performance summary — scoring avg + handicap breakdown |
|
|
85
|
+
| `arccos scoring` | Scoring trend with visual bar chart |
|
|
83
86
|
| `arccos courses` | List all courses you've played |
|
|
84
87
|
| `arccos pace` | Pace of play analysis by course (color-coded) |
|
|
85
88
|
| `arccos stats <id>` | Strokes gained analysis for a round |
|
|
86
|
-
| `arccos export` | Export rounds to JSON, CSV, or NDJSON |
|
|
89
|
+
| `arccos export` | Export rounds to JSON, CSV, or NDJSON (`--detail` for hole data) |
|
|
87
90
|
| `arccos logout` | Clear cached credentials |
|
|
88
91
|
|
|
89
92
|
Every command supports `--json` for raw JSON output and `--help` for usage info.
|
|
@@ -92,9 +95,10 @@ Every command supports `--json` for raw JSON output and `--help` for usage info.
|
|
|
92
95
|
|
|
93
96
|
```bash
|
|
94
97
|
# Rounds
|
|
95
|
-
arccos rounds # last 20 rounds
|
|
98
|
+
arccos rounds # last 20 rounds (with +/- and course names)
|
|
96
99
|
arccos rounds -n 50 # last 50
|
|
97
100
|
arccos rounds --after 2025-01-01 # filter by date
|
|
101
|
+
arccos rounds --course "pebble" # filter by course name (substring)
|
|
98
102
|
arccos rounds --json # raw JSON
|
|
99
103
|
|
|
100
104
|
# Round detail
|
|
@@ -108,6 +112,11 @@ arccos handicap --history # revision history
|
|
|
108
112
|
arccos clubs # active clubs with make/model
|
|
109
113
|
arccos clubs --after 2025-01-01 # distances from this year only
|
|
110
114
|
|
|
115
|
+
# Performance
|
|
116
|
+
arccos bests # all-time personal bests
|
|
117
|
+
arccos overview # scoring avg + handicap breakdown
|
|
118
|
+
arccos scoring # scoring trend with bar chart
|
|
119
|
+
|
|
111
120
|
# Courses
|
|
112
121
|
arccos courses # all courses played
|
|
113
122
|
|
|
@@ -117,6 +126,7 @@ arccos pace -n 20 # last 20 rounds only
|
|
|
117
126
|
|
|
118
127
|
# Export
|
|
119
128
|
arccos export -f csv -o rounds.csv # export to CSV file
|
|
129
|
+
arccos export --detail # include hole-by-hole data
|
|
120
130
|
arccos export -f json # dump JSON to stdout
|
|
121
131
|
arccos export -f ndjson # newline-delimited JSON
|
|
122
132
|
```
|
|
@@ -237,7 +247,7 @@ for club in bag["clubs"]:
|
|
|
237
247
|
|
|
238
248
|
# Courses played
|
|
239
249
|
for course in client.courses.played():
|
|
240
|
-
print(f" {course.get('
|
|
250
|
+
print(f" {course.get('name', course['courseId'])}")
|
|
241
251
|
|
|
242
252
|
# Pace of play analysis
|
|
243
253
|
pace = client.rounds.pace_of_play()
|
|
@@ -289,12 +299,11 @@ All data calls go to `https://api.arccosgolf.com` with `Authorization: Bearer <t
|
|
|
289
299
|
{
|
|
290
300
|
"roundId": 18294051,
|
|
291
301
|
"courseId": 12450,
|
|
292
|
-
"
|
|
302
|
+
"courseVersion": 4,
|
|
293
303
|
"startTime": "2025-09-14T15:30:00.000000Z",
|
|
294
304
|
"endTime": "2025-09-14T20:15:00.000000Z",
|
|
295
305
|
"noOfShots": 79,
|
|
296
306
|
"noOfHoles": 18,
|
|
297
|
-
"overUnder": 7,
|
|
298
307
|
"holes": [
|
|
299
308
|
{
|
|
300
309
|
"holeId": 1, "noOfShots": 4, "putts": 2,
|
|
@@ -334,7 +343,7 @@ python3 -m venv .venv
|
|
|
334
343
|
source .venv/bin/activate
|
|
335
344
|
pip install -e ".[dev]"
|
|
336
345
|
|
|
337
|
-
pytest # tests with coverage (
|
|
346
|
+
pytest # tests with coverage (88%+, 132 tests)
|
|
338
347
|
ruff check arccos/ # lint
|
|
339
348
|
mypy arccos/ # type check
|
|
340
349
|
```
|
|
@@ -42,14 +42,17 @@ arccos clubs # smart club distances
|
|
|
42
42
|
| Command | Description |
|
|
43
43
|
|---------|-------------|
|
|
44
44
|
| `arccos login` | Authenticate and cache credentials to `~/.arccos_creds.json` |
|
|
45
|
-
| `arccos rounds` | List recent rounds with date, score,
|
|
45
|
+
| `arccos rounds` | List recent rounds with date, score, +/-, course name |
|
|
46
46
|
| `arccos round <id>` | Hole-by-hole detail (score, putts, FIR, GIR) with totals |
|
|
47
47
|
| `arccos handicap` | Handicap breakdown by category (overall, driving, approach, etc.) |
|
|
48
48
|
| `arccos clubs` | Smart club distances with make/model, range, and shot count |
|
|
49
|
+
| `arccos bests` | All-time personal bests (lowest score, longest drive, etc.) |
|
|
50
|
+
| `arccos overview` | Performance summary — scoring avg + handicap breakdown |
|
|
51
|
+
| `arccos scoring` | Scoring trend with visual bar chart |
|
|
49
52
|
| `arccos courses` | List all courses you've played |
|
|
50
53
|
| `arccos pace` | Pace of play analysis by course (color-coded) |
|
|
51
54
|
| `arccos stats <id>` | Strokes gained analysis for a round |
|
|
52
|
-
| `arccos export` | Export rounds to JSON, CSV, or NDJSON |
|
|
55
|
+
| `arccos export` | Export rounds to JSON, CSV, or NDJSON (`--detail` for hole data) |
|
|
53
56
|
| `arccos logout` | Clear cached credentials |
|
|
54
57
|
|
|
55
58
|
Every command supports `--json` for raw JSON output and `--help` for usage info.
|
|
@@ -58,9 +61,10 @@ Every command supports `--json` for raw JSON output and `--help` for usage info.
|
|
|
58
61
|
|
|
59
62
|
```bash
|
|
60
63
|
# Rounds
|
|
61
|
-
arccos rounds # last 20 rounds
|
|
64
|
+
arccos rounds # last 20 rounds (with +/- and course names)
|
|
62
65
|
arccos rounds -n 50 # last 50
|
|
63
66
|
arccos rounds --after 2025-01-01 # filter by date
|
|
67
|
+
arccos rounds --course "pebble" # filter by course name (substring)
|
|
64
68
|
arccos rounds --json # raw JSON
|
|
65
69
|
|
|
66
70
|
# Round detail
|
|
@@ -74,6 +78,11 @@ arccos handicap --history # revision history
|
|
|
74
78
|
arccos clubs # active clubs with make/model
|
|
75
79
|
arccos clubs --after 2025-01-01 # distances from this year only
|
|
76
80
|
|
|
81
|
+
# Performance
|
|
82
|
+
arccos bests # all-time personal bests
|
|
83
|
+
arccos overview # scoring avg + handicap breakdown
|
|
84
|
+
arccos scoring # scoring trend with bar chart
|
|
85
|
+
|
|
77
86
|
# Courses
|
|
78
87
|
arccos courses # all courses played
|
|
79
88
|
|
|
@@ -83,6 +92,7 @@ arccos pace -n 20 # last 20 rounds only
|
|
|
83
92
|
|
|
84
93
|
# Export
|
|
85
94
|
arccos export -f csv -o rounds.csv # export to CSV file
|
|
95
|
+
arccos export --detail # include hole-by-hole data
|
|
86
96
|
arccos export -f json # dump JSON to stdout
|
|
87
97
|
arccos export -f ndjson # newline-delimited JSON
|
|
88
98
|
```
|
|
@@ -203,7 +213,7 @@ for club in bag["clubs"]:
|
|
|
203
213
|
|
|
204
214
|
# Courses played
|
|
205
215
|
for course in client.courses.played():
|
|
206
|
-
print(f" {course.get('
|
|
216
|
+
print(f" {course.get('name', course['courseId'])}")
|
|
207
217
|
|
|
208
218
|
# Pace of play analysis
|
|
209
219
|
pace = client.rounds.pace_of_play()
|
|
@@ -255,12 +265,11 @@ All data calls go to `https://api.arccosgolf.com` with `Authorization: Bearer <t
|
|
|
255
265
|
{
|
|
256
266
|
"roundId": 18294051,
|
|
257
267
|
"courseId": 12450,
|
|
258
|
-
"
|
|
268
|
+
"courseVersion": 4,
|
|
259
269
|
"startTime": "2025-09-14T15:30:00.000000Z",
|
|
260
270
|
"endTime": "2025-09-14T20:15:00.000000Z",
|
|
261
271
|
"noOfShots": 79,
|
|
262
272
|
"noOfHoles": 18,
|
|
263
|
-
"overUnder": 7,
|
|
264
273
|
"holes": [
|
|
265
274
|
{
|
|
266
275
|
"holeId": 1, "noOfShots": 4, "putts": 2,
|
|
@@ -300,7 +309,7 @@ python3 -m venv .venv
|
|
|
300
309
|
source .venv/bin/activate
|
|
301
310
|
pip install -e ".[dev]"
|
|
302
311
|
|
|
303
|
-
pytest # tests with coverage (
|
|
312
|
+
pytest # tests with coverage (88%+, 132 tests)
|
|
304
313
|
ruff check arccos/ # lint
|
|
305
314
|
mypy arccos/ # type check
|
|
306
315
|
```
|
|
@@ -9,6 +9,8 @@ Quick start:
|
|
|
9
9
|
print(client.handicap.current())
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
from importlib.metadata import version as _pkg_version
|
|
13
|
+
|
|
12
14
|
from .auth import ArccosAuth
|
|
13
15
|
from .client import ArccosClient
|
|
14
16
|
from .exceptions import (
|
|
@@ -19,7 +21,7 @@ from .exceptions import (
|
|
|
19
21
|
ArccosRateLimitError,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
|
-
__version__ = "
|
|
24
|
+
__version__ = _pkg_version("arccos-api")
|
|
23
25
|
__all__ = [
|
|
24
26
|
"ArccosClient",
|
|
25
27
|
"ArccosAuth",
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
Internal HTTP client for the Arccos API.
|
|
3
3
|
|
|
4
4
|
All API calls share a single requests.Session with automatic token refresh.
|
|
5
|
+
Retries transient failures (429, 5xx, timeouts) with exponential backoff.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
import logging
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
10
13
|
from typing import Any
|
|
11
14
|
|
|
12
15
|
import requests
|
|
@@ -17,6 +20,9 @@ from .exceptions import raise_for_status
|
|
|
17
20
|
logger = logging.getLogger(__name__)
|
|
18
21
|
|
|
19
22
|
API_BASE = "https://api.arccosgolf.com"
|
|
23
|
+
DEFAULT_TIMEOUT = 15
|
|
24
|
+
MAX_RETRIES = 3
|
|
25
|
+
RETRY_BACKOFF = 1.0 # seconds; doubles each retry
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
class HttpClient:
|
|
@@ -26,6 +32,7 @@ class HttpClient:
|
|
|
26
32
|
- Prepends the API base URL
|
|
27
33
|
- Injects the ``Authorization: Bearer`` header on every request
|
|
28
34
|
- Auto-refreshes the JWT when expired
|
|
35
|
+
- Retries on transient failures (429, 5xx, timeouts)
|
|
29
36
|
- Raises typed :mod:`arccos.exceptions` for non-2xx responses
|
|
30
37
|
"""
|
|
31
38
|
|
|
@@ -37,6 +44,7 @@ class HttpClient:
|
|
|
37
44
|
"Content-Type": "application/json",
|
|
38
45
|
"Accept": "application/json",
|
|
39
46
|
})
|
|
47
|
+
self._timeout = int(os.environ.get("ARCCOS_TIMEOUT", DEFAULT_TIMEOUT))
|
|
40
48
|
|
|
41
49
|
# ------------------------------------------------------------------
|
|
42
50
|
# Public interface
|
|
@@ -90,11 +98,44 @@ class HttpClient:
|
|
|
90
98
|
url = f"{API_BASE}{path}"
|
|
91
99
|
logger.debug("%s %s", method, url)
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
last_exc: Exception | None = None
|
|
102
|
+
for attempt in range(MAX_RETRIES):
|
|
103
|
+
try:
|
|
104
|
+
resp = self._session.request(
|
|
105
|
+
method, url, headers=headers,
|
|
106
|
+
timeout=self._timeout, verify=True, **kwargs,
|
|
107
|
+
)
|
|
108
|
+
except requests.exceptions.Timeout as exc:
|
|
109
|
+
last_exc = exc
|
|
110
|
+
self._backoff(attempt)
|
|
111
|
+
continue
|
|
112
|
+
except requests.exceptions.ConnectionError as exc:
|
|
113
|
+
last_exc = exc
|
|
114
|
+
self._backoff(attempt)
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
if resp.ok:
|
|
118
|
+
if resp.content:
|
|
119
|
+
return resp.json()
|
|
120
|
+
return {}
|
|
121
|
+
|
|
122
|
+
# Retry on 429 and 5xx; raise immediately on other errors
|
|
123
|
+
if resp.status_code == 429 or resp.status_code >= 500:
|
|
124
|
+
try:
|
|
125
|
+
raise_for_status(resp)
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
last_exc = exc
|
|
128
|
+
self._backoff(attempt)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# Non-retryable error (4xx other than 429)
|
|
132
|
+
raise_for_status(resp)
|
|
133
|
+
|
|
134
|
+
# Exhausted retries
|
|
135
|
+
raise last_exc # type: ignore[misc]
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _backoff(attempt: int) -> None:
|
|
139
|
+
delay = RETRY_BACKOFF * (2 ** attempt)
|
|
140
|
+
logger.warning("Retrying in %.1fs (attempt %d/%d)", delay, attempt + 1, MAX_RETRIES)
|
|
141
|
+
time.sleep(delay)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
arccos CLI — pull your Arccos Golf data from the terminal.
|
|
3
|
+
|
|
4
|
+
Uses the reverse-engineered Arccos Golf API. Not affiliated with Arccos Golf LLC.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from . import auth, rounds, stats
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group()
|
|
15
|
+
@click.version_option(package_name="arccos-api", prog_name="arccos")
|
|
16
|
+
def cli():
|
|
17
|
+
"""
|
|
18
|
+
\b
|
|
19
|
+
arccos — Your Arccos Golf data in the terminal.
|
|
20
|
+
|
|
21
|
+
\b
|
|
22
|
+
Quick start:
|
|
23
|
+
arccos login Authenticate and save credentials
|
|
24
|
+
arccos rounds Your recent rounds
|
|
25
|
+
arccos round <id> Hole-by-hole round detail
|
|
26
|
+
arccos handicap Current handicap index
|
|
27
|
+
arccos clubs Smart club distances
|
|
28
|
+
arccos courses Courses you've played
|
|
29
|
+
arccos pace Pace of play by course
|
|
30
|
+
arccos stats Strokes gained analysis
|
|
31
|
+
arccos bests Personal bests (all-time records)
|
|
32
|
+
arccos overview Overall stats summary
|
|
33
|
+
arccos scoring Scoring trend over recent rounds
|
|
34
|
+
arccos export Export rounds to JSON/CSV
|
|
35
|
+
arccos logout Clear cached credentials
|
|
36
|
+
|
|
37
|
+
\b
|
|
38
|
+
Credentials are cached in ~/.arccos_creds.json after first login.
|
|
39
|
+
Set ARCCOS_EMAIL and ARCCOS_PASSWORD to skip the prompts.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Register all command submodules
|
|
44
|
+
auth.register(cli)
|
|
45
|
+
rounds.register(cli)
|
|
46
|
+
stats.register(cli)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main():
|
|
50
|
+
cli()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Shared helpers for CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
err_console = Console(stderr=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_client():
|
|
17
|
+
"""Build an ArccosClient from env vars or cached creds, with a friendly error."""
|
|
18
|
+
from arccos import ArccosClient
|
|
19
|
+
from arccos.exceptions import ArccosAuthError
|
|
20
|
+
|
|
21
|
+
email = os.environ.get("ARCCOS_EMAIL")
|
|
22
|
+
password = os.environ.get("ARCCOS_PASSWORD")
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return ArccosClient(email=email, password=password)
|
|
26
|
+
except ArccosAuthError as e:
|
|
27
|
+
err_console.print(f"[red]Auth failed:[/red] {e}")
|
|
28
|
+
err_console.print("Run [bold]arccos login[/bold] or set ARCCOS_EMAIL and ARCCOS_PASSWORD.")
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
except ValueError:
|
|
31
|
+
err_console.print(
|
|
32
|
+
"[red]Not logged in.[/red] Run [bold]arccos login[/bold] first, or set "
|
|
33
|
+
"ARCCOS_EMAIL and ARCCOS_PASSWORD environment variables."
|
|
34
|
+
)
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _output_json(data):
|
|
39
|
+
click.echo(json.dumps(data, indent=2))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _flag(minutes: int) -> str:
|
|
43
|
+
if minutes > 300:
|
|
44
|
+
return "\U0001f534"
|
|
45
|
+
if minutes > 270:
|
|
46
|
+
return "\U0001f7e1"
|
|
47
|
+
return "\U0001f7e2"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_course_map(client) -> tuple[dict[int, str], dict[int, int]]:
|
|
51
|
+
"""Build courseId → courseName and courseId → par lookups from played courses."""
|
|
52
|
+
try:
|
|
53
|
+
played = client.courses.played()
|
|
54
|
+
names = {
|
|
55
|
+
c["courseId"]: c.get("courseName") or c.get("name") or f"Course {c['courseId']}"
|
|
56
|
+
for c in played
|
|
57
|
+
}
|
|
58
|
+
pars = {
|
|
59
|
+
c["courseId"]: c["mensPar"]
|
|
60
|
+
for c in played
|
|
61
|
+
if c.get("mensPar")
|
|
62
|
+
}
|
|
63
|
+
return names, pars
|
|
64
|
+
except Exception:
|
|
65
|
+
return {}, {}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _score_color(score: int, par: int | None) -> str:
|
|
69
|
+
"""Return a Rich color string based on score relative to par."""
|
|
70
|
+
if par is None:
|
|
71
|
+
return "bold"
|
|
72
|
+
diff = score - par
|
|
73
|
+
if diff <= -2:
|
|
74
|
+
return "bold yellow" # eagle or better
|
|
75
|
+
if diff == -1:
|
|
76
|
+
return "bold green" # birdie
|
|
77
|
+
if diff == 0:
|
|
78
|
+
return "white" # par
|
|
79
|
+
if diff == 1:
|
|
80
|
+
return "red" # bogey
|
|
81
|
+
return "bold red" # double+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Login and logout CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from ._helpers import console, err_console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(cli: click.Group) -> None:
|
|
13
|
+
cli.add_command(login)
|
|
14
|
+
cli.add_command(logout)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.option("--email", envvar="ARCCOS_EMAIL", prompt="Arccos email",
|
|
19
|
+
help="Your Arccos account email.")
|
|
20
|
+
@click.option("--password", envvar="ARCCOS_PASSWORD", prompt="Password",
|
|
21
|
+
hide_input=True, help="Your Arccos password.")
|
|
22
|
+
def login(email: str, password: str):
|
|
23
|
+
"""Authenticate and save credentials to ~/.arccos_creds.json."""
|
|
24
|
+
from arccos import ArccosClient
|
|
25
|
+
from arccos.exceptions import ArccosAuthError
|
|
26
|
+
|
|
27
|
+
with console.status("Logging in\u2026"):
|
|
28
|
+
try:
|
|
29
|
+
client = ArccosClient(email=email, password=password)
|
|
30
|
+
except ArccosAuthError as e:
|
|
31
|
+
err_console.print(f"[red]Login failed:[/red] {e}")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
console.print(f"[green]\u2713[/green] Logged in as [bold]{client.email}[/bold]")
|
|
35
|
+
console.print(f" User ID: [dim]{client.user_id}[/dim]")
|
|
36
|
+
console.print(" Credentials cached at [dim]~/.arccos_creds.json[/dim]")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.command()
|
|
40
|
+
def logout():
|
|
41
|
+
"""Clear cached credentials (~/.arccos_creds.json)."""
|
|
42
|
+
from arccos.auth import DEFAULT_CREDS_PATH
|
|
43
|
+
|
|
44
|
+
if DEFAULT_CREDS_PATH.exists():
|
|
45
|
+
DEFAULT_CREDS_PATH.unlink()
|
|
46
|
+
console.print(
|
|
47
|
+
"[green]\u2713[/green] Credentials removed from "
|
|
48
|
+
"[bold]~/.arccos_creds.json[/bold]"
|
|
49
|
+
)
|
|
50
|
+
console.print(
|
|
51
|
+
"[dim]Note: Server-side tokens are not revoked. "
|
|
52
|
+
"The access key remains valid until it expires (~180 days).[/dim]"
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
console.print("[dim]No cached credentials found.[/dim]")
|