lbd-followback 0.1.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.
- lbd_followback-0.1.0/.gitignore +7 -0
- lbd_followback-0.1.0/.python-version +1 -0
- lbd_followback-0.1.0/AGENTS.md +67 -0
- lbd_followback-0.1.0/LICENSE +674 -0
- lbd_followback-0.1.0/PKG-INFO +82 -0
- lbd_followback-0.1.0/README.md +61 -0
- lbd_followback-0.1.0/lbd_followback/__init__.py +5 -0
- lbd_followback-0.1.0/lbd_followback/__main__.py +62 -0
- lbd_followback-0.1.0/lbd_followback/cache.py +27 -0
- lbd_followback-0.1.0/lbd_followback/compare.py +18 -0
- lbd_followback-0.1.0/lbd_followback/config.py +15 -0
- lbd_followback-0.1.0/lbd_followback/parser.py +66 -0
- lbd_followback-0.1.0/lbd_followback/report.py +122 -0
- lbd_followback-0.1.0/lbd_followback/scraper.py +62 -0
- lbd_followback-0.1.0/lbd_followback/transport.py +50 -0
- lbd_followback-0.1.0/main.py +4 -0
- lbd_followback-0.1.0/pyproject.toml +35 -0
- lbd_followback-0.1.0/uv.lock +352 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# AGENTS.md — lbd-followback
|
|
2
|
+
|
|
3
|
+
## Stack
|
|
4
|
+
|
|
5
|
+
- Python 3.14+, managed via `uv` (not pip/conda)
|
|
6
|
+
- `letterboxdpy` for scraping (NOT raw requests/BS4 — it handles TLS fingerprinting and retries)
|
|
7
|
+
- `rich` for terminal output
|
|
8
|
+
- `curl-cffi` (transitive via letterboxdpy) — Chrome impersonation, not stdlib `requests`
|
|
9
|
+
|
|
10
|
+
## Commands
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
lbd-followback <username> # after uv tool install .
|
|
14
|
+
uv run python -m lbd_followback <username> # or via uv
|
|
15
|
+
uv run python main.py <username> # same, thin wrapper
|
|
16
|
+
uv run python -m lbd_followback <username> -f # bypass 24h cache
|
|
17
|
+
uv add <pkg> # add dep
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
No test/lint/typecheck scripts exist.
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
main.py → thin wrapper → lbd_followback.__main__.main()
|
|
26
|
+
lbd_followback/
|
|
27
|
+
__main__.py → CLI entrypoint (argparse)
|
|
28
|
+
__init__.py → re-exports scrape, analyze, generate
|
|
29
|
+
transport.py → HTTP session, retry, 429 backoff (depends on curl-cffi)
|
|
30
|
+
parser.py → BS4 HTML parsing + number extraction
|
|
31
|
+
scraper.py → pagination orchestration (uses transport + parser + cache)
|
|
32
|
+
cache.py → JSON disk cache with 24h TTL
|
|
33
|
+
compare.py → set math: mutual / not_following_back / not_followed_back
|
|
34
|
+
report.py → HTML report generation
|
|
35
|
+
config.py → constants (DOMAIN, delay, cache TTL)
|
|
36
|
+
~/.cache/lbd-followback/ → cached JSON (auto-created)
|
|
37
|
+
CWD/<username>_report.html → output (default)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Key constraints
|
|
41
|
+
|
|
42
|
+
- **Do not use letterboxdpy.User.get_following() directly** — it paginates internally without inter-page delay, triggering rate limits. Use `scraper.scrape()` which adds 3-4s delay between pages.
|
|
43
|
+
- **DOMAIN** must come from `config.py` (`https://letterboxd.com`), never hardcoded.
|
|
44
|
+
- **Number extraction** uses `parser.extract_number()` (regex), not ad-hoc `re.search`.
|
|
45
|
+
- **429 handling**: exponential backoff 30s→60s→120s in `transport.py`. Do not change without testing against real LBD rate limits.
|
|
46
|
+
- **Cache**: `~/.cache/lbd-followback/`, 24h TTL. Non-existent users are NOT cached (404 returns empty without saving).
|
|
47
|
+
- **No parallel requests** — sequential only. Parallel triggers rate limits immediately.
|
|
48
|
+
- **Entry point**: `lbd-followback = "lbd_followback.__main__:main"` in `[project.scripts]`. Also `python -m lbd_followback` works via `__main__.py`.
|
|
49
|
+
- **Public API** re-exported from `__init__.py`: `scrape`, `analyze`, `generate`.
|
|
50
|
+
- `rich.console.Console` is used for all user-facing output (not `print`).
|
|
51
|
+
|
|
52
|
+
## Modules & boundaries
|
|
53
|
+
|
|
54
|
+
| Module | Depends on | Never import |
|
|
55
|
+
|--------|-----------|--------------|
|
|
56
|
+
| transport.py | curl-cffi, bs4, rich | scraper, parser, cache |
|
|
57
|
+
| parser.py | bs4, config | transport, scraper, cache |
|
|
58
|
+
| scraper.py | transport, parser, cache | curl-cffi, bs4 directly |
|
|
59
|
+
| cache.py | config, json, pathlib | scraper, transport, parser |
|
|
60
|
+
| compare.py | nothing | scraper, transport, parser, cache |
|
|
61
|
+
| report.py | config, rich | scraper, transport, parser, cache |
|
|
62
|
+
|
|
63
|
+
## SOLID notes (applied)
|
|
64
|
+
|
|
65
|
+
- transport.py abstracts HTTP (swap curl-cffi → httpx by changing one file)
|
|
66
|
+
- parser.py abstracts BS4 (swap to selectolax by changing one file)
|
|
67
|
+
- scraper.py is an orchestrator only — no HTTP or parsing logic
|