{"id":38679308,"url":"https://github.com/nmcassa/letterboxdpy","last_synced_at":"2026-05-10T18:28:25.937Z","repository":{"id":38843925,"uuid":"504659127","full_name":"nmcassa/letterboxdpy","owner":"nmcassa","description":"A letterboxd webscraper ","archived":false,"fork":false,"pushed_at":"2026-02-02T00:17:25.000Z","size":2421,"stargazers_count":124,"open_issues_count":3,"forks_count":25,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-02-02T10:43:57.629Z","etag":null,"topics":["api","json","letterboxd","library","movie","movies","python","webscraper"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/letterboxdpy/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nmcassa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-06-17T20:07:33.000Z","updated_at":"2026-02-02T00:15:55.000Z","dependencies_parsed_at":"2024-02-02T01:45:15.346Z","dependency_job_id":"aaac42d2-5b08-4d98-9e86-e020857baa0b","html_url":"https://github.com/nmcassa/letterboxdpy","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/nmcassa/letterboxdpy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmcassa%2Fletterboxdpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmcassa%2Fletterboxdpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmcassa%2Fletterboxdpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmcassa%2Fletterboxdpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nmcassa","download_url":"https://codeload.github.com/nmcassa/letterboxdpy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nmcassa%2Fletterboxdpy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29486072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T15:33:17.885Z","status":"ssl_error","status_checked_at":"2026-02-15T15:32:53.698Z","response_time":118,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","json","letterboxd","library","movie","movies","python","webscraper"],"created_at":"2026-01-17T10:10:32.909Z","updated_at":"2026-05-10T18:28:25.926Z","avatar_url":"https://github.com/nmcassa.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\r\n  letterboxdpy\r\n\u003c/h1\u003e\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003cstrong\u003eA Python library for Letterboxd data\u003c/strong\u003e\u003cbr\u003e\r\n  \u003csub\u003eSimple, modern, and easy-to-use toolkit for movies, users, and more.\u003c/sub\u003e\r\n\u003c/p\u003e\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003ca href=\"https://pypi.org/project/letterboxdpy/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/v/letterboxdpy?color=blue\u0026style=flat-square\" alt=\"PyPI version\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://pypi.org/project/letterboxdpy/\"\u003e\u003cimg src=\"https://img.shields.io/pypi/pyversions/letterboxdpy?color=blue\u0026style=flat-square\" alt=\"Python Version\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://github.com/nmcassa/letterboxdpy/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/pypi/l/letterboxdpy?color=blue\u0026style=flat-square\" alt=\"License\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://pepy.tech/project/letterboxdpy\"\u003e\u003cimg src=\"https://static.pepy.tech/personalized-badge/letterboxdpy?period=total\u0026units=none\u0026left_color=grey\u0026right_color=blue\u0026left_text=Downloads\u0026style=flat-square\" alt=\"Downloads\"\u003e\u003c/a\u003e\r\n  \u003ca href=\"https://github.com/nmcassa/letterboxdpy/actions/workflows/health-check.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/nmcassa/letterboxdpy/health-check.yml?style=flat-square\u0026label=Health%20Check\" alt=\"Weekly DOM Health Check\"\u003e\u003c/a\u003e\r\n\u003c/p\u003e\r\n\r\n---\r\n\r\n\u003ch1 id=\"installation\"\u003eInstallation\u003c/h1\u003e\r\n\r\n### From PyPI\r\n\r\nYou can easily install the stable version of `letterboxdpy` from PyPI using pip:\r\n\r\n```bash\r\npip install letterboxdpy\r\n```\r\n\r\n### From GitHub Repository\r\n\r\nAlternatively, if you wish to access the latest (potentially unstable) version directly from the GitHub repository, you can execute the following command:\r\n\r\n```bash\r\npip install git+https://github.com/nmcassa/letterboxdpy.git\r\n```\r\n\r\n### Local Installation (for Development)\r\n\r\nIf 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:\r\n\r\n```bash\r\npip install -e .\r\n\r\n# Or with example dependencies\r\npip install -e \".[examples]\"\r\n```\r\n\r\n\r\n\u003e [!WARNING]\r\n\u003e 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.\r\n\r\n\u003ch1 id=\"core-objects\"\u003eCore Objects\u003c/h1\u003e\r\n\r\n\u003ch2 id=\"user\"\u003eUser Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/user.py) | [Functions Documentation](/docs/user/funcs/)\r\n\r\n```python\r\nfrom letterboxdpy.user import User\r\nuser_instance = User(\"nmcassa\")\r\nprint(user_instance)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eUser\u003c/code\u003e object response\u003c/summary\u003e\r\n  \r\n```json\r\n{\r\n  \"username\": \"nmcassa\",\r\n  \"url\": \"https://letterboxd.com/nmcassa\",\r\n  \"id\": 1500306,\r\n  \"is_hq\": false,\r\n  \"display_name\": \"nmcassa\",\r\n  \"bio\": null,\r\n  \"location\": null,\r\n  \"website\": null,\r\n  \"watchlist_length\": 76,\r\n  \"stats\": {\r\n    \"films\": 702,\r\n    \"this_year\": 7,\r\n    \"lists\": 2,\r\n    \"following\": 8,\r\n    \"followers\": 8\r\n  },\r\n  \"favorites\": {\r\n    \"51794\": {\r\n      \"slug\": \"the-king-of-comedy\",\r\n      \"name\": \"The King of Comedy\",\r\n      \"url\": \"https://letterboxd.com/film/the-king-of-comedy/\",\r\n      \"year\": 1982,\r\n      \"log_url\": \"https://letterboxd.com/nmcassa/film/the-king-of-comedy/activity/\"\r\n    },\r\n    \"...\": \"...\"\r\n  },\r\n  \"avatar\": {\r\n    \"exists\": true,\r\n    \"upscaled\": true,\r\n    \"url\": \"https://a.ltrbxd.com/resized/avatar/upload/1/5/0/0/3/0/6/shard/avtr-0-1000-0-1000-crop.jpg\"\r\n  },\r\n  \"recent\": {\r\n    \"watchlist\": {\r\n      \"703077\": {\r\n        \"id\": \"703077\",\r\n        \"slug\": \"magazine-dreams\",\r\n        \"name\": \"Magazine Dreams\",\r\n        \"year\": 2023\r\n      },\r\n      \"...\": \"...\"\r\n    },\r\n    \"diary\": {\r\n      \"months\": {\r\n        \"1\": {\r\n          \"31\": [\r\n            {\r\n              \"name\": \"If I Had Legs I'd Kick You\",\r\n              \"slug\": \"if-i-had-legs-id-kick-you\"\r\n            }\r\n          ],\r\n          \"...\": \"...\"\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"movie\"\u003eMovie Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/movie.py) | [Functions Documentation](/docs/movie/funcs/)\r\n\r\n```python\r\nfrom letterboxdpy.movie import Movie\r\n\r\n# lookup by slug\r\nmovie_instance = Movie(\"v-for-vendetta\")\r\n\r\n# lookup by external ids\r\nmovie_instance = Movie(tmdb=752)\r\nmovie_instance = Movie(imdb=\"tt0434409\")\r\n\r\n# or using factory methods\r\nmovie_instance = Movie.from_tmdb(752)\r\nmovie_instance = Movie.from_imdb(\"tt0434409\")\r\n\r\nprint(movie_instance)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eMovie\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"url\": \"https://letterboxd.com/film/v-for-vendetta\",\r\n  \"slug\": \"v-for-vendetta\",\r\n  \"id\": \"51400\",\r\n  \"title\": \"V for Vendetta\",\r\n  \"original_title\": null,\r\n  \"runtime\": 132,\r\n  \"rating\": 3.84,\r\n  \"year\": 2005,\r\n  \"tmdb_link\": \"https://www.themoviedb.org/movie/752/\",\r\n  \"tmdb_id\": \"752\",\r\n  \"imdb_link\": \"http://www.imdb.com/title/tt0434409/maindetails\",\r\n  \"imdb_id\": \"tt0434409\",\r\n  \"poster\": \"https://a.ltrbxd.com/resized/film-poster/5/1/4/0/0/51400-v-for-vendetta-0-230-0-345-crop.jpg\",\r\n  \"banner\": \"https://a.ltrbxd.com/resized/sm/upload/mx/jg/tz/ni/v-for-vendetta-1920-1920-1080-1080-crop-000000.jpg\",\r\n  \"tagline\": \"People should not be afraid of their governments. Governments should be afraid of their people.\",\r\n  \"description\": \"In a world in which Great Britain has become a fascist state...\",\r\n  \"trailer\": {\r\n    \"id\": \"3ge0navn9E0\",\r\n    \"link\": \"https://www.youtube.com/watch?v=3ge0navn9E0\",\r\n    \"embed_url\": \"https://www.youtube.com/embed/3ge0navn9E0\"\r\n  },\r\n  \"alternative_titles\": [\r\n    \"Vendetta \\u00fc\\u00e7\\u00fcn V\",\r\n    \"O za osvetu\",...\r\n  ],\r\n  \"details\": [\r\n    {\r\n      \"type\": \"studio\",\r\n      \"name\": \"Virtual Studios\",\r\n      \"slug\": \"virtual-studios\",\r\n      \"url\": \"https://letterboxd.com/studio/virtual-studios/\"\r\n    },\r\n    \"...\"\r\n  ],\r\n  \"genres\": [\r\n    {\r\n      \"type\": \"genre\",\r\n      \"name\": \"Thriller\",\r\n      \"slug\": \"thriller\",\r\n      \"url\": \"https://letterboxd.com/films/genre/thriller/\"\r\n    },\r\n    \"...\"\r\n  ],\r\n  \"cast\": [\r\n    {\r\n      \"name\": \"Natalie Portman\",\r\n      \"role_name\": \"Evey Hammond\",\r\n      \"slug\": \"natalie-portman\",\r\n      \"url\": \"https://letterboxd.com/actor/natalie-portman/\"\r\n    },\r\n    \"...\"\r\n  ],\r\n  \"crew\": {\r\n    \"director\": [\r\n      {\r\n        \"name\": \"James McTeigue\",\r\n        \"slug\": \"james-mcteigue\",\r\n        \"url\": \"https://letterboxd.com/director/james-mcteigue/\"\r\n      }\r\n    ],\r\n    \"...\": \"...\"\r\n  },\r\n  \"popular_reviews\": [\r\n    {\r\n      \"user\": {\r\n        \"username\": \"zoeyluke\",\r\n        \"display_name\": \"zoey luke\"\r\n      },\r\n      \"link\": \"https://letterboxd.com/zoeyluke/film/v-for-vendetta/3/\",\r\n      \"rating\": 4.5,\r\n      \"review\": \"I love natalie Portman and I hate the government\"\r\n    },\r\n    \"...\": \"...\"\r\n  ]\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"search\"\u003eSearch Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/search.py) | [Functions Documentation](/docs/search/funcs/)\r\n\r\n```python\r\nfrom letterboxdpy.search import Search\r\nsearch_instance = Search(\"V for Vendetta\", 'films')\r\nprint(search_instance.get_results(5))\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eSearch\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"available\": true,\r\n  \"query\": \"V%20for%20Vendetta\",\r\n  \"filter\": \"films\",\r\n  \"end_page\": 1,\r\n  \"count\": 5,\r\n  \"results\": [\r\n    {\r\n      \"no\": 1,\r\n      \"page\": 1,\r\n      \"type\": \"film\",\r\n      \"slug\": \"v-for-vendetta\",\r\n      \"name\": \"V for Vendetta\",\r\n      \"year\": 2005,\r\n      \"url\": \"https://letterboxd.com/film/v-for-vendetta/\",\r\n      \"poster\": \"https://s.ltrbxd.com/static/img/empty-poster-70-BSf-Pjrh.png\",\r\n      \"directors\": [\r\n        {\r\n          \"name\": \"James McTeigue\",\r\n          \"slug\": \"james-mcteigue\",\r\n          \"url\": \"https://letterboxd.com/director/james-mcteigue/\"\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"no\": 2,\r\n      \"page\": 1,\r\n      \"type\": \"film\",\r\n      \"slug\": \"lady-vengeance\",\r\n      \"name\": \"Lady Vengeance\",\r\n      \"year\": 2005,\r\n      \"url\": \"https://letterboxd.com/film/lady-vengeance/\",\r\n      \"poster\": null,\r\n      \"directors\": [\r\n        {\r\n          \"name\": \"Park Chan-wook\",\r\n          \"slug\": \"park-chan-wook\",\r\n          \"url\": \"https://letterboxd.com/director/park-chan-wook/\"\r\n        }\r\n      ]\r\n    },...\r\n  ]\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"list\"\u003eList Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/list.py)\r\n\r\n```python\r\nfrom letterboxdpy.list import List\r\nlist_instance = List(\"nmcassa\", \"movies-to-watch-with-priscilla-park\")\r\nlist_instance.movies # fetch movies (lazy loading)\r\nprint(list_instance)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eList\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"username\": \"nmcassa\",\r\n  \"slug\": \"movies-to-watch-with-priscilla-park\",\r\n  \"url\": \"https://letterboxd.com/nmcassa/list/movies-to-watch-with-priscilla-park\",\r\n  \"list_id\": \"31052453\",\r\n  \"title\": \"Movies to Watch with Priscilla Park\",\r\n  \"author\": \"nmcassa\",\r\n  \"count\": 2,\r\n  \"date_created\": \"2024-05-18T16:44:57.013000Z\",\r\n  \"date_updated\": \"2024-05-20T14:58:06.486000Z\",\r\n  \"description\": null,\r\n  \"tags\": [],\r\n  \"_movies\": {\r\n    \"240344\": {\r\n      \"slug\": \"la-la-land\",\r\n      \"name\": \"La La Land\",\r\n      \"year\": 2016,\r\n      \"url\": \"https://letterboxd.com/film/la-la-land/\"\r\n    },\r\n    \"...\": \"...\"\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"members\"\u003eMembers Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/members.py) | [Functions Documentation](/docs/members/funcs/)\r\n\r\n```python\r\nfrom letterboxdpy.members import Members\r\nmembers_instance = Members(max=5)\r\nprint(members_instance.members)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eMembers\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n[\r\n  \"schaffrillas\",\r\n  \"kurstboy\",\r\n  \"demiadejuyigbe\",\r\n  \"zoerosebryant\",\r\n  \"jaragon23\"\r\n]\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"films\"\u003eFilms Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/films.py) | [Functions Documentation](/docs/films/funcs/)\r\n\r\n```python\r\nfrom letterboxdpy.films import Films\r\nfilms_instance = Films(\"https://letterboxd.com/films/popular/\", max=3)\r\nprint(films_instance.movies)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eFilms\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"1197499\": {\r\n    \"slug\": \"marty-supreme\",\r\n    \"name\": \"Marty Supreme\",\r\n    \"rating\": 4.21,\r\n    \"url\": \"https://letterboxd.com/film/marty-supreme/\"\r\n  },\r\n  \"772232\": {\r\n    \"slug\": \"hamnet\",\r\n    \"name\": \"Hamnet\",\r\n    \"rating\": 4.22,\r\n    \"url\": \"https://letterboxd.com/film/hamnet/\"\r\n  },\r\n  \"1116600\": {\r\n    \"slug\": \"sinners-2025\",\r\n    \"name\": \"Sinners\",\r\n    \"rating\": 4.11,\r\n    \"url\": \"https://letterboxd.com/film/sinners-2025/\"\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch2 id=\"watchlist\"\u003eWatchlist Object\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/watchlist.py)\r\n\r\n```python\r\nfrom letterboxdpy.watchlist import Watchlist\r\nwatchlist = Watchlist(\"nmcassa\")\r\nwatchlist.movies # fetch movies (lazy loading)\r\nprint(watchlist)\r\n```\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003eClick to expand \u003ccode\u003eWatchlist\u003c/code\u003e object response\u003c/summary\u003e\r\n\r\n```json\r\n{\r\n  \"username\": \"nmcassa\",\r\n  \"url\": \"https://letterboxd.com/nmcassa/watchlist\",\r\n  \"count\": 134,\r\n  \"movies\": {\r\n    \"51315\": {\r\n      \"slug\": \"videodrome\",\r\n      \"name\": \"Videodrome\",\r\n      \"year\": 1983,\r\n      \"url\": \"https://letterboxd.com/film/videodrome/\"\r\n    },\r\n    \"...\": \"...\"\r\n  }\r\n}\r\n```\r\n\u003c/details\u003e\r\n\r\n\u003ch1 id=\"advanced-features\"\u003eAdvanced Features\u003c/h1\u003e\r\n\r\n\u003ch2 id=\"authentication\"\u003eAuthentication \u0026 Sessions\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/auth.py)\r\n\r\nThe `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.\r\n\r\n```python\r\nfrom letterboxdpy.auth import UserSession\r\n\r\n# Logs in if no session exists, or loads the saved session automatically\r\nsession = UserSession.ensure()\r\n\r\n# 2. Programmatic login\r\n# session = UserSession.login(\"username\", \"password\")\r\n\r\n# 3. Manual load from custom path\r\n# session = UserSession.load(Path(\".cookie/session.json\"))\r\n\r\nprint(f\"Authenticated as: {session.username}\")\r\n```\r\n\r\n\u003ch2 id=\"settings\"\u003eUser Settings\u003c/h2\u003e\r\n\r\n[Explore the file](letterboxdpy/account/settings.py)\r\n\r\nThe `UserSettings` module allows reading and updating user profile and notification settings. **Requires an authenticated session.**\r\n\r\n```python\r\nfrom letterboxdpy.account.settings import UserSettings\r\n\r\nsettings = UserSettings(session)\r\n\r\n# Get current profile data\r\nprofile = settings.get_profile()\r\nprint(f\"Current Bio: {profile['bio']}\")\r\n\r\n# Update specific profile fields\r\nsettings.update_profile({\r\n    \"location\": \"New York, USA\",\r\n    \"website\": \"https://example.com\",\r\n    \"bio\": \"Just another movie lover.\"\r\n})\r\n\r\n# Manage notifications\r\nnotifs = settings.get_notifications()\r\nsettings.update_notifications({\"emailEditorial\": True, \"pushFollowers\": False})\r\n```\r\n\r\n\u003ch1 id=\"development\"\u003eDevelopment\u003c/h1\u003e\r\n\r\n\u003ch2 id=\"requirements\"\u003eRequirements\u003c/h2\u003e\r\n\r\nThis project requires **Python 3.10 or higher**.\r\n\r\n```bash\r\n# Core installation\r\npip install letterboxdpy\r\n\r\n# With example dependencies\r\npip install \"letterboxdpy[examples]\"\r\n```\r\n\r\n\r\n\u003ch2 id=\"examples\"\u003eExamples\u003c/h2\u003e\r\n\r\nExample scripts demonstrating various features are available in the [`examples/`](examples/) directory.\r\n\r\nSee [`examples/README.md`](examples/README.md) for detailed usage instructions.\r\n\r\n\u003ch2 id=\"linting\"\u003eLinting\u003c/h2\u003e\r\n\r\nThis project uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting, configured in [`pyproject.toml`](pyproject.toml).\r\n\r\n```bash\r\n# Check for issues\r\nruff check .\r\n\r\n# Auto-fix issues\r\nruff check --fix .\r\n\r\n# Format code\r\nruff format .\r\n```\r\n\r\n\u003ch2 id=\"testing\"\u003eTesting\u003c/h2\u003e\r\n\r\nRun the full test suite using `pytest`:\r\n\r\n```bash\r\npython -m pytest tests\r\n```\r\n\r\nOr run a specific test file:\r\n\r\n```bash\r\npython -m pytest tests/test_movie.py\r\n```\r\n\r\n\u003e [!NOTE]\r\n\u003e 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.\r\n\r\n\u003ch2 id=\"pre-commit-hooks\"\u003ePre-commit Hooks\u003c/h2\u003e\r\n\r\nPre-commit hooks automatically run Ruff and the test suite before every commit, ensuring no broken or non-compliant code enters the repository.\r\n\r\n```bash\r\n# Install hooks (one-time setup)\r\npre-commit install\r\n\r\n# Run manually against all files\r\npre-commit run --all-files\r\n```\r\n\r\n\u003ch2 id=\"ci-pipeline\"\u003eCI Pipeline\u003c/h2\u003e\r\n\r\nGitHub Actions automatically runs linting and tests on every push and pull request against the `main` branch, across **Python 3.10, 3.11, and 3.12**.\r\n\r\nSee [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for the full pipeline configuration.\r\n\r\n---\r\n\r\n## Contributors\r\n\r\n\u003ca href=\"https://github.com/nmcassa/letterboxdpy/graphs/contributors\"\u003e\r\n  \u003cimg src=\"https://contrib.rocks/image?repo=nmcassa/letterboxdpy\" /\u003e\r\n\u003c/a\u003e\r\n\r\n---\r\n\r\n## License\r\n\r\n**[MIT License](LICENSE)** — Free to use, modify, and share.\r\n\r\n---\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003csub\u003e\u003cstrong\u003eStargazers over time:\u003c/strong\u003e\u003c/sub\u003e\u003cbr\u003e\r\n  \u003ca href=\"https://starchart.cc/nmcassa/letterboxdpy\"\u003e\r\n    \u003cimg src=\"https://starchart.cc/nmcassa/letterboxdpy.svg?background=%2300000000\u0026axis=%23848D97\u0026line=%23238636\" alt=\"Stargazers over time\"\u003e\r\n  \u003c/a\u003e\u003cbr\u003e\r\n  \u003csub\u003eBuilt by the Letterboxdpy community\u003c/sub\u003e\r\n\u003c/p\u003e\r","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnmcassa%2Fletterboxdpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnmcassa%2Fletterboxdpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnmcassa%2Fletterboxdpy/lists"}