letterboxdpy
A Python library for Letterboxd data
Simple, modern, and easy-to-use toolkit for movies, users, and more.
Installation
From PyPI
You can easily install the stable version ofletterboxdpy from PyPI using pip:
``bash
pip install letterboxdpy
`
From GitHub Repository
Alternatively, if you wish to access the latest (potentially unstable) version directly from the GitHub repository, you can execute the following command:
`bash
pip install git+https://github.com/nmcassa/letterboxdpy.git
`
Local Installation (for Development)
If you have cloned the repository locally and want to make changes to the code, it is recommended to install it in "editable" mode. This allows you to run the library files directly and see your changes reflected immediately:
`bash
pip install -e .
Or with example dependencies
pip install -e ".[examples]"
`
[!WARNING]
Please be aware that installing directly from the GitHub repository or locally might give you access to the most recent features and bug fixes, but it could also include changes that haven't been thoroughly tested and may not be stable for production use.
Core Objects
User Object
Explore the file | Functions Documentation
`python
from letterboxdpy.user import User
user_instance = User("nmcassa")
print(user_instance)
`
Click to expand User object response
`json
{
"username": "nmcassa",
"url": "https://letterboxd.com/nmcassa",
"id": 1500306,
"badge": "member",
"display_name": "nmcassa",
"bio": null,
"location": null,
"website": null,
"watchlist_length": 76,
"stats": {
"films": 702,
"this_year": 7,
"lists": 2,
"following": 8,
"followers": 8
},
"favorites": {
"51794": {
"slug": "the-king-of-comedy",
"name": "The King of Comedy",
"url": "https://letterboxd.com/film/the-king-of-comedy/",
"year": 1982,
"log_url": "https://letterboxd.com/nmcassa/film/the-king-of-comedy/activity/"
},
"...": "..."
},
"avatar": {
"exists": true,
"upscaled": true,
"url": "https://a.ltrbxd.com/resized/avatar/upload/1/5/0/0/3/0/6/shard/avtr-0-1000-0-1000-crop.jpg"
},
"backdrop": null,
"recent": {
"watchlist": {
"703077": {
"id": "703077",
"slug": "magazine-dreams",
"name": "Magazine Dreams",
"year": 2023
},
"...": "..."
},
"diary": {
"months": {
"1": {
"31": [
{
"name": "If I Had Legs I'd Kick You",
"slug": "if-i-had-legs-id-kick-you"
}
],
"...": "..."
}
}
}
}
}
`
Movie Object
Explore the file | Functions Documentation
`python
from letterboxdpy.movie import Movie
lookup by slug
movie_instance = Movie("v-for-vendetta")
lookup by external ids
movie_instance = Movie(tmdb=752)
movie_instance = Movie(imdb="tt0434409")
or using factory methods
movie_instance = Movie.from_tmdb(752)
movie_instance = Movie.from_imdb("tt0434409")
print(movie_instance)
`
Click to expand Movie object response
`json
{
"url": "https://letterboxd.com/film/v-for-vendetta",
"slug": "v-for-vendetta",
"id": "51400",
"title": "V for Vendetta",
"original_title": null,
"runtime": 132,
"rating": 3.84,
"year": 2005,
"tmdb_link": "https://www.themoviedb.org/movie/752/",
"tmdb_id": "752",
"imdb_link": "http://www.imdb.com/title/tt0434409/maindetails",
"imdb_id": "tt0434409",
"poster": "https://a.ltrbxd.com/resized/film-poster/5/1/4/0/0/51400-v-for-vendetta-0-230-0-345-crop.jpg",
"banner": "https://a.ltrbxd.com/resized/sm/upload/mx/jg/tz/ni/v-for-vendetta-1920-1920-1080-1080-crop-000000.jpg",
"tagline": "People should not be afraid of their governments. Governments should be afraid of their people.",
"description": "In a world in which Great Britain has become a fascist state...",
"trailer": {
"id": "3ge0navn9E0",
"link": "https://www.youtube.com/watch?v=3ge0navn9E0",
"embed_url": "https://www.youtube.com/embed/3ge0navn9E0"
},
"alternative_titles": [
"Vendetta \u00fc\u00e7\u00fcn V",
"O za osvetu",...
],
"details": [
{
"type": "studio",
"name": "Virtual Studios",
"slug": "virtual-studios",
"url": "https://letterboxd.com/studio/virtual-studios/"
},
"..."
],
"genres": [
{
"type": "genre",
"name": "Thriller",
"slug": "thriller",
"url": "https://letterboxd.com/films/genre/thriller/"
},
"..."
],
"cast": [
{
"name": "Natalie Portman",
"role_name": "Evey Hammond",
"slug": "natalie-portman",
"url": "https://letterboxd.com/actor/natalie-portman/"
},
"..."
],
"crew": {
"director": [
{
"name": "James McTeigue",
"slug": "james-mcteigue",
"url": "https://letterboxd.com/director/james-mcteigue/"
}
],
"...": "..."
},
"popular_reviews": [
{
"user": {
"username": "zoeyluke",
"display_name": "zoey luke"
},
"link": "https://letterboxd.com/zoeyluke/film/v-for-vendetta/3/",
"rating": 4.5,
"review": "I love natalie Portman and I hate the government"
},
"...": "..."
]
}
`
Search Object
Explore the file | Functions Documentation
`python
from letterboxdpy.search import Search
search_instance = Search("V for Vendetta", 'films')
print(search_instance.get_results(5))
`
Click to expand Search object response
`json
{
"available": true,
"query": "V%20for%20Vendetta",
"filter": "films",
"end_page": 1,
"count": 5,
"results": [
{
"no": 1,
"page": 1,
"type": "film",
"slug": "v-for-vendetta",
"name": "V for Vendetta",
"year": 2005,
"url": "https://letterboxd.com/film/v-for-vendetta/",
"poster": "https://s.ltrbxd.com/static/img/empty-poster-70-BSf-Pjrh.png",
"directors": [
{
"name": "James McTeigue",
"slug": "james-mcteigue",
"url": "https://letterboxd.com/director/james-mcteigue/"
}
]
},
{
"no": 2,
"page": 1,
"type": "film",
"slug": "lady-vengeance",
"name": "Lady Vengeance",
"year": 2005,
"url": "https://letterboxd.com/film/lady-vengeance/",
"poster": null,
"directors": [
{
"name": "Park Chan-wook",
"slug": "park-chan-wook",
"url": "https://letterboxd.com/director/park-chan-wook/"
}
]
},...
]
}
`
List Object
Explore the file
`python
from letterboxdpy.list import List
list_instance = List("nmcassa", "movies-to-watch-with-priscilla-park")
list_instance.movies # fetch movies (lazy loading)
print(list_instance)
`
Click to expand List object response
`json
{
"username": "nmcassa",
"slug": "movies-to-watch-with-priscilla-park",
"url": "https://letterboxd.com/nmcassa/list/movies-to-watch-with-priscilla-park",
"list_id": "31052453",
"title": "Movies to Watch with Priscilla Park",
"author": "nmcassa",
"count": 2,
"date_created": "2024-05-18T16:44:57.013000Z",
"date_updated": "2024-05-20T14:58:06.486000Z",
"description": null,
"tags": [],
"_movies": {
"240344": {
"slug": "la-la-land",
"name": "La La Land",
"year": 2016,
"url": "https://letterboxd.com/film/la-la-land/"
},
"...": "..."
}
}
`
Members Object
Explore the file | Functions Documentation
`python
from letterboxdpy.members import Members
members_instance = Members(max=5)
print(members_instance.members)
`
Click to expand Members object response
`json
[
"schaffrillas",
"kurstboy",
"demiadejuyigbe",
"zoerosebryant",
"jaragon23"
]
`
Films Object
Explore the file | Functions Documentation
`python
from letterboxdpy.films import Films
films_instance = Films("https://letterboxd.com/films/popular/", max=3)
print(films_instance.movies)
`
Click to expand Films object response
`json
{
"1197499": {
"slug": "marty-supreme",
"name": "Marty Supreme",
"rating": 4.21,
"url": "https://letterboxd.com/film/marty-supreme/"
},
"772232": {
"slug": "hamnet",
"name": "Hamnet",
"rating": 4.22,
"url": "https://letterboxd.com/film/hamnet/"
},
"1116600": {
"slug": "sinners-2025",
"name": "Sinners",
"rating": 4.11,
"url": "https://letterboxd.com/film/sinners-2025/"
}
}
`
Watchlist Object
Explore the file
`python
from letterboxdpy.watchlist import Watchlist
watchlist = Watchlist("nmcassa")
watchlist.movies # fetch movies (lazy loading)
print(watchlist)
`
Click to expand Watchlist object response
`json
{
"username": "nmcassa",
"url": "https://letterboxd.com/nmcassa/watchlist",
"count": 134,
"movies": {
"51315": {
"slug": "videodrome",
"name": "Videodrome",
"year": 1983,
"url": "https://letterboxd.com/film/videodrome/"
},
"...": "..."
}
}
`
Advanced Features
Authentication & Sessions
Explore the file
The UserSession module unlocks account-specific features like profile customization and settings management. It handles login securely and persists your session, so you don't have to sign in every time.
`python
from letterboxdpy.auth import UserSession
Logs in if no session exists, or loads the saved session automatically
session = UserSession.ensure()
2. Programmatic login
session = UserSession.login("username", "password")
3. Manual load from custom path
session = UserSession.load(Path(".cookie/session.json"))
print(f"Authenticated as: {session.username}")
`
Click to expand UserSession demo output
`text
Authenticated as: nmcassa
Session CSRF token: 9a8b7c6d5e4f3a2b1c0d
`
User Settings
Explore the file
The UserSettings module allows reading and updating user profile and notification settings. Requires an authenticated session.
`python
from letterboxdpy.account.settings import UserSettings
settings = UserSettings(session)
Get current profile data
profile = settings.get_profile()
print(f"Current Bio: {profile['bio']}")
Update specific profile fields
settings.update_profile({
"location": "New York, USA",
"website": "https://example.com",
"bio": "Just another movie lover."
})
Manage notifications
notifs = settings.get_notifications()
settings.update_notifications({"emailEditorial": True, "pushFollowers": False})
`
Click to expand UserSettings demo response
`json
{
"__csrf": "9a8b7c6d5e4f3a2b1c0d",
"completeSettings": "true",
"givenName": "Jane",
"familyName": "Doe",
"location": "New York, USA",
"website": "https://example.com",
"bio": "Just another movie lover.",
"favouriteFilms": [
{
"id": "51400",
"name": "V for Vendetta"
},
{
"id": "47447",
"name": "Inception"
},
{
"id": "240344",
"name": "La La Land"
},
{
"id": "20956",
"name": "Black Swan"
}
]
}
`
Development
Requirements
This project requires Python 3.10 or higher.
`bash
Core installation
pip install letterboxdpy
With example dependencies
pip install "letterboxdpy[examples]"
`
Examples
Example scripts demonstrating various features are available in the examples/ directory.
See examples/README.md for detailed usage instructions.
Linting
This project uses Ruff for linting and formatting, configured in pyproject.toml.
`bash
Check for issues
ruff check .
Auto-fix issues
ruff check --fix .
Format code
ruff format .
`
Testing
The test suite separates fast, deterministic offline unit tests from live external network tests (marked as smoke).
Run Offline Unit Tests (Default)
Runs offline parser and model validation in ~0.5 seconds without network calls:
`bash
python -m pytest
`
Run Live Network Tests (Smoke Marker)
Executes tests against live Letterboxd pages:
`bash
python -m pytest -m smoke
`
Run All Tests
`bash
python -m pytest -m "smoke or not smoke"
`
[!NOTE]
Tests that require an authenticated Letterboxd session (e.g.
test_auth.py) are automatically skipped if no valid .cookie file is present. This ensures the suite runs cleanly in CI environments without credentials.
Pre-commit Hooks
Pre-commit hooks automatically format code, check linting rules with Ruff, and validate commit title length before each commit:
- Ruff & Ruff-Format: Runs on commit to check and auto-format staged files.
- Commit Title Length: Validates that the first line of the commit message follows Conventional Commits and is at or under 62 characters (checked on
commit-msg).
`bash
Install hooks for both pre-commit and commit-msg stages
pre-commit install --hook-type pre-commit --hook-type commit-msg
Run manually against all files
pre-commit run --all-files
`
CI Pipeline
GitHub Actions automatically runs linting and tests on every push and pull request against the main branch, across all supported Python versions.
See .github/workflows/ci.yml` for the full pipeline configuration.
---