From 7385a2446e29c8712aab71a823b7f54a3858dada Mon Sep 17 00:00:00 2001 From: Thibault Ducret Date: Tue, 29 Sep 2026 16:30:28 +0200 Subject: [PATCH 1/2] Skip unnecessary showtime requests --- allocine/client.py | 7 + ...showtimes_skips_to_next_showtime_date.json | 22 + ...owtimes_stops_after_no_showtime_error.json | 1 + ...showtimes_skips_to_next_showtime_date.json | 1124 +++++++++++++++++ ...owtimes_stops_after_no_showtime_error.json | 123 ++ tests/test_allocine.py | 14 + 6 files changed, 1291 insertions(+) create mode 100644 tests/__snapshots__/test_allocine/test_get_showtimes_skips_to_next_showtime_date.json create mode 100644 tests/__snapshots__/test_allocine/test_get_showtimes_stops_after_no_showtime_error.json create mode 100644 tests/cassettes/get_showtimes_skips_to_next_showtime_date.json create mode 100644 tests/cassettes/get_showtimes_stops_after_no_showtime_error.json diff --git a/allocine/client.py b/allocine/client.py index ba07c4a..6726702 100644 --- a/allocine/client.py +++ b/allocine/client.py @@ -158,6 +158,13 @@ def get_showtimes( theater_id=theater_id, date=requested_date, ) + if response.get("error") is True and response.get("message") == "no.showtime.error": + break + if response.get("error") is True and response.get("message") == "next.showtime.on": + next_date = Date.fromisoformat(response["nextDate"]) + if next_date > requested_date: + requested_date = next_date + continue day_showtimes = self._parse_showtimes(response.get("results") or []) for page in range(2, (jmespath.search("pagination.totalPages", response) or 1) + 1): response = self._client.get_showtimelist_by_theater_id( diff --git a/tests/__snapshots__/test_allocine/test_get_showtimes_skips_to_next_showtime_date.json b/tests/__snapshots__/test_allocine/test_get_showtimes_skips_to_next_showtime_date.json new file mode 100644 index 0000000..e6f3f42 --- /dev/null +++ b/tests/__snapshots__/test_allocine/test_get_showtimes_skips_to_next_showtime_date.json @@ -0,0 +1,22 @@ +[ + { + "date_time": "2026-10-02T20:30:00.000000", + "movie": { + "actors": "Pauline Clément, Laurent Stocker, Julien Frison", + "countries": [ + "France" + ], + "directors": "Martin Darondeau, Bertrand Usclat", + "duration": "datetime.timedelta(seconds=4500)", + "genres": "Comédie", + "language": "Français", + "movie_id": 1000037046, + "original_title": "De la Comédie-Française", + "rating": 3.96, + "screen_format": "Numérique", + "synopsis": "Dans 3 heures, Nina dévoile sa première mise en scène à la Comédie-Française. Mais dans l’agitation des dernières répétitions, rien ne se passe comme prévu : retards, coups de stress, problèmes techniques et problèmes d’égo secouent la troupe. Pourtant, Nina n’a pas d’autre choix que d’aller jusqu’au bout car s’il y a bien une règle d’or à la Comédie-Française, c’est qu’on n’annule pas. Commence alors une course contre la montre pour sauver la représentation.", + "title": "De la Comédie-Française", + "year": 2026 + } + } +] diff --git a/tests/__snapshots__/test_allocine/test_get_showtimes_stops_after_no_showtime_error.json b/tests/__snapshots__/test_allocine/test_get_showtimes_stops_after_no_showtime_error.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/tests/__snapshots__/test_allocine/test_get_showtimes_stops_after_no_showtime_error.json @@ -0,0 +1 @@ +[] diff --git a/tests/cassettes/get_showtimes_skips_to_next_showtime_date.json b/tests/cassettes/get_showtimes_skips_to_next_showtime_date.json new file mode 100644 index 0000000..f9134cd --- /dev/null +++ b/tests/cassettes/get_showtimes_skips_to_next_showtime_date.json @@ -0,0 +1,1124 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "uri": "https://www.allocine.fr/_/showtimes/theater-W1021/d-2026-09-26/", + "body": null, + "headers": {} + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "date": [ + "Tue, 29 Sep 2026 14:28:02 GMT" + ], + "content-type": [ + "application/json" + ], + "cache-control": [ + "max-age=3600, public, stale-if-error=172800, stale-while-revalidate=86400" + ], + "vary": [ + "Origin, accept-encoding" + ], + "server": [ + "cloudflare" + ], + "via": [ + "1.1 google" + ], + "alt-svc": [ + "h3=\":443\"; ma=86400" + ], + "last-modified": [ + "Tue, 29 Sep 2026 14:28:02 GMT" + ], + "cf-cache-status": [ + "MISS" + ], + "server-timing": [ + "cfEdge;dur=29,cfOrigin;dur=34" + ], + "report-to": [ + "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=5NvXtwrZrBfshcRuG2u57WKEvKsJNVko4zzbfeDTnhpTMoXQ7XTVdqafkfbAdg0rTRf9cSLzgtaTFJD2ev1k0Pdc2SrwFqdb5UzIg0eBuMDxz4qupd8AV5UtgvjQw9ZsJQ%3D%3D\"}]}" + ], + "nel": [ + "{\"report_to\":\"cf-nel\",\"success_fraction\":0.01,\"max_age\":604800}" + ], + "cf-ray": [ + "a42bac0cc9870082-CDG" + ] + }, + "body": { + "error": true, + "message": "next.showtime.on", + "messageParam": null, + "nextDate": "2026-10-02", + "results": [], + "params": { + "experience": null, + "projection": null, + "comfort": null, + "picture": null, + "sound": null, + "versionDetail": null, + "page": 1 + }, + "facets": [ + { + "__typename": "Facets", + "name": "showtimes.projection", + "values": [ + { + "key": "Digital", + "translation": { + "tag": "DIGITAL", + "name": "Numérique" + } + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.experience", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.comfort", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.version_detail", + "values": [ + { + "key": "Local", + "versionDetail": "LOCAL" + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.picture", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.sound", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.service", + "values": [] + } + ], + "pagination": { + "page": 1, + "totalPages": 0, + "itemsPerPage": 15, + "totalItems": 0 + }, + "data": null + } + } + }, + { + "request": { + "method": "GET", + "uri": "https://www.allocine.fr/_/showtimes/theater-W1021/d-2026-10-02/", + "body": null, + "headers": {} + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "date": [ + "Tue, 29 Sep 2026 14:28:02 GMT" + ], + "content-type": [ + "application/json" + ], + "cache-control": [ + "max-age=3600, public, stale-if-error=172800, stale-while-revalidate=86400" + ], + "vary": [ + "Origin, accept-encoding" + ], + "server": [ + "cloudflare" + ], + "via": [ + "1.1 google" + ], + "alt-svc": [ + "h3=\":443\"; ma=86400" + ], + "age": [ + "399" + ], + "last-modified": [ + "Tue, 29 Sep 2026 14:21:23 GMT" + ], + "cf-cache-status": [ + "HIT" + ], + "server-timing": [ + "cfEdge;dur=20,cfOrigin;dur=0" + ], + "report-to": [ + "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=vyhu5%2BN2OSAjTiM5KqWW0NbMdNkKMRDwh5WYHVRHOs15OW9LFOKUtXrUqPp7nwazXYN3h0%2FurkJqIjC5H3b7p%2BdtwikQePK9MLHlnxHBeRloLuy03TJgUpe5Z0byopIq1g%3D%3D\"}]}" + ], + "nel": [ + "{\"report_to\":\"cf-nel\",\"success_fraction\":0.01,\"max_age\":604800}" + ], + "cf-ray": [ + "a42bac0dbf1ad70e-CDG" + ] + }, + "body": { + "error": false, + "message": null, + "messageParam": null, + "nextDate": null, + "results": [ + { + "movie": { + "synopsis": "Dans 3 heures, Nina dévoile sa première mise en scène à la Comédie-Française. Mais dans l’agitation des dernières répétitions, rien ne se passe comme prévu : retards, coups de stress, problèmes techniques et problèmes d’égo secouent la troupe. Pourtant, Nina n’a pas d’autre choix que d’aller jusqu’au bout car s’il y a bien une règle d’or à la Comédie-Française, c’est qu’on n’annule pas. Commence alors une course contre la montre pour sauver la représentation.", + "synopsis_json": { + "__typename": "BodyJson", + "body": [ + { + "type": "paragraph", + "children": [ + { + "text": "Dans 3 heures, Nina dévoile sa première mise en scène à la Comédie-Française. Mais dans l’agitation des dernières répétitions, rien ne se passe comme prévu : retards, coups de stress, problèmes techniques et problèmes d’égo secouent la troupe. Pourtant, Nina n’a pas d’autre choix que d’aller jusqu’au bout car s’il y a bien une règle d’or à la Comédie-Française, c’est qu’on n’annule pas. Commence alors une course contre la montre pour sauver la représentation." + } + ] + } + ] + }, + "__typename": "Movie", + "internalId": 1000037046, + "id": "TW92aWU6MTAwMDAzNzA0Ng==", + "poster": { + "__typename": "InternalImage", + "id": "SW1hZ2U6OTAwMjA3MjIzMg==", + "internalId": 9002072232, + "url": "https://fr.web.img2.acsta.net/img/9c/4d/9c4daa6e94f7107c83f7d732700c44a0.jpg", + "path": "/img/9c/4d/9c4daa6e94f7107c83f7d732700c44a0.jpg" + }, + "title": "De la Comédie-Française", + "originalTitle": "De la Comédie-Française", + "type": "FEATURE_FILM", + "runtime": "1h 15min", + "genres": [ + { + "id": 13005, + "translate": "Comédie", + "tag": "COMEDY" + } + ], + "languages": [ + "FRENCH" + ], + "data": { + "__typename": "MovieData", + "seo": { + "__typename": "Seo", + "browsable": true, + "title": null + }, + "productionYear": 2026, + "rules": { + "rating": { + "has_warning_message": false, + "has_mandatory_review": false + } + } + }, + "stats": { + "userRating": { + "score": 3.96, + "count": 5535 + }, + "userReview": { + "count": 1182 + }, + "pressReview": { + "score": 4.1, + "count": 41 + }, + "metric": { + "hits": 1777374, + "sessions": 852835 + }, + "wantToSeeCount": 2989 + }, + "editorialReviews": [], + "releases": [ + { + "__typename": "MovieRelease", + "releaseDate": { + "__typename": "PartialDate", + "date": "2026-07-22", + "precision": "day" + }, + "name": "Released", + "certificate": { + "__typename": "Certificate", + "code": "U", + "label": "Tout public" + }, + "advice": null, + "companyLimitation": null, + "releaseTags": { + "tagTypes": [ + "THEATER" + ], + "tagFlags": null + }, + "visaNumber": "166502" + } + ], + "credits": [ + { + "person": { + "__typename": "Person", + "internalId": 808059, + "lastName": "Darondeau", + "firstName": "Martin", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "position": { + "__typename": "Position", + "name": "DIRECTOR", + "department": "DIRECTION" + }, + "rank": 1 + }, + { + "person": { + "__typename": "Person", + "internalId": 701659, + "lastName": "Usclat", + "firstName": "Bertrand", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "position": { + "__typename": "Position", + "name": "DIRECTOR", + "department": "DIRECTION" + }, + "rank": 2 + } + ], + "cast": { + "__typename": "castMemberConnection", + "edges": [ + { + "node": { + "actor": { + "__typename": "Person", + "internalId": 722075, + "lastName": "Clément", + "firstName": "Pauline", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 1 + } + }, + { + "node": { + "actor": { + "__typename": "Person", + "internalId": 133471, + "lastName": "Stocker", + "firstName": "Laurent", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 2 + } + }, + { + "node": { + "actor": { + "__typename": "Person", + "internalId": 179737, + "lastName": "Frison", + "firstName": "Julien", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 3 + } + } + ], + "nodes": [ + { + "actor": { + "__typename": "Person", + "internalId": 722075, + "lastName": "Clément", + "firstName": "Pauline", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 1 + }, + { + "actor": { + "__typename": "Person", + "internalId": 133471, + "lastName": "Stocker", + "firstName": "Laurent", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 2 + }, + { + "actor": { + "__typename": "Person", + "internalId": 179737, + "lastName": "Frison", + "firstName": "Julien", + "seo": { + "__typename": "Seo", + "browsable": true + } + }, + "voiceActor": null, + "originalVoiceActor": null, + "rank": 3 + } + ] + }, + "countries": [ + { + "__typename": "Country", + "id": 5001, + "name": "France", + "localizedName": "France" + } + ], + "relatedTags": [ + { + "__typename": "Tag", + "internalId": 1270, + "name": "Compte à rebours", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Keyword" + ] + } + }, + { + "__typename": "Tag", + "internalId": 667, + "name": "Course contre la montre", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Characteristic" + ] + } + }, + { + "__typename": "Tag", + "internalId": 2104, + "name": "Théâtre et salle de spectacle", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Environnement" + ] + } + }, + { + "__typename": "Tag", + "internalId": 1753, + "name": "Réalisateur / Réalisatrice", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Keyword" + ] + } + }, + { + "__typename": "Tag", + "internalId": 732, + "name": "Acteur / Actrice", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Keyword" + ] + } + }, + { + "__typename": "Tag", + "internalId": 5257, + "name": "Rire", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Envies" + ] + } + }, + { + "__typename": "Tag", + "internalId": 2703, + "name": "Situation comique / Gag", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.Ambiance" + ] + } + }, + { + "__typename": "Tag", + "internalId": 5259, + "name": "Art et essai", + "scope": null, + "data": { + "seo": { + "browsable": true, + "friendly": false, + "scope": { + "userRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "pressRating": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "popularity": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + }, + "ranking": { + "movie": { + "title": null, + "subTitle": null, + "description": null + }, + "series": { + "title": null, + "subTitle": null, + "description": null + } + } + } + } + }, + "tags": { + "list": [ + "Tag.Type.PublicVise" + ] + } + } + ], + "flags": { + "hasDvdRelease": false, + "hasNews": true, + "hasOnlineProduct": false, + "hasOnlineRelease": false, + "hasPhysicalProduct": false, + "hasPreview": false, + "hasShowtime": true, + "hasSoundtrack": false, + "hasTheaterRelease": true, + "hasTrivia": true, + "hasAwards": true, + "isLesIndes": true, + "isIncontestable": false, + "isClubApproved": [], + "hasClubNews": [], + "isComingSoon": false, + "isPlayingNow": true, + "tvRelease": false, + "hasTeaser": true, + "hasTrailer": false + }, + "customFlags": { + "isPremiere": false, + "weeklyOuting": false + }, + "synopsisFull": "

Dans 3 heures, Nina dévoile sa première mise en scène à la Comédie-Française. Mais dans l’agitation des dernières répétitions, rien ne se passe comme prévu : retards, coups de stress, problèmes techniques et problèmes d’égo secouent la troupe. Pourtant, Nina n’a pas d’autre choix que d’aller jusqu’au bout car s’il y a bien une règle d’or à la Comédie-Française, c’est qu’on n’annule pas. Commence alors une course contre la montre pour sauver la représentation.

" + }, + "showtimes": { + "original": [], + "original_st": [], + "original_sme": [], + "multiple": [ + { + "__typename": "Showtime", + "internalId": 81216058339, + "startsAt": "2026-10-02T20:30:00", + "timeBeforeStart": "PT0H0M0S", + "service": null, + "experience": null, + "comfort": null, + "projection": [ + "DIGITAL" + ], + "picture": null, + "sound": null, + "tags": [ + "Localization.Version.Original", + "Localization.Language.French", + "Format.Projection.Digital" + ], + "diffusionVersion": "LOCAL", + "data": { + "__typename": "ShowtimeData", + "ticketing": [ + { + "__typename": "Ticketing", + "urls": [ + "https://relay.mvtx.us/ticketing/dbz?code_theater=W1021&gid_entity=movie.movie._.1000037046&tags=Localization.Version.Original,Localization.Language.French,Format.Projection.Digital&perfd=20261002&perft=20:30" + ], + "type": "DESKTOP", + "provider": "relay" + } + ] + }, + "isPreview": false, + "isWeeklyMovieOuting": false + } + ], + "multiple_st": [], + "multiple_sme": [] + } + } + ], + "params": { + "experience": null, + "projection": null, + "comfort": null, + "picture": null, + "sound": null, + "versionDetail": null, + "page": 1 + }, + "facets": [ + { + "__typename": "Facets", + "name": "showtimes.projection", + "values": [ + { + "key": "Digital", + "translation": { + "tag": "DIGITAL", + "name": "Numérique" + } + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.experience", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.comfort", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.version_detail", + "values": [ + { + "key": "Local", + "versionDetail": "LOCAL" + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.picture", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.sound", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.service", + "values": [] + } + ], + "pagination": { + "page": 1, + "totalPages": 1, + "itemsPerPage": 15, + "totalItems": 1 + }, + "data": null + } + } + } + ] +} diff --git a/tests/cassettes/get_showtimes_stops_after_no_showtime_error.json b/tests/cassettes/get_showtimes_stops_after_no_showtime_error.json new file mode 100644 index 0000000..117ced8 --- /dev/null +++ b/tests/cassettes/get_showtimes_stops_after_no_showtime_error.json @@ -0,0 +1,123 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "uri": "https://www.allocine.fr/_/showtimes/theater-W0746/d-2026-09-29/", + "body": null, + "headers": {} + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "date": [ + "Tue, 29 Sep 2026 14:24:56 GMT" + ], + "content-type": [ + "application/json" + ], + "cache-control": [ + "max-age=3600, public, stale-if-error=172800, stale-while-revalidate=86400" + ], + "vary": [ + "Origin, accept-encoding" + ], + "server": [ + "cloudflare" + ], + "via": [ + "1.1 google" + ], + "alt-svc": [ + "h3=\":443\"; ma=86400" + ], + "last-modified": [ + "Tue, 29 Sep 2026 14:24:56 GMT" + ], + "cf-cache-status": [ + "MISS" + ], + "server-timing": [ + "cfEdge;dur=22,cfOrigin;dur=31" + ], + "report-to": [ + "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=Ym%2FPStyB1TH%2BlYONMO%2F2yGpW6er%2BWXzBcZJpMnXYV0E7gySf0we3geiYpP%2FTJOoN80SU%2B2l%2FF3oiT0rfipoFiqPdH3Dyr1UK9vZL4iUrjAvMDmZYDzNsbbiTfhSpkB75sQ%3D%3D\"}]}" + ], + "nel": [ + "{\"report_to\":\"cf-nel\",\"success_fraction\":0.01,\"max_age\":604800}" + ], + "cf-ray": [ + "a42ba77ebd1dd12e-CDG" + ] + }, + "body": { + "error": true, + "message": "no.showtime.error", + "messageParam": { + "day": "2026-09-29", + "page": 1 + }, + "nextDate": null, + "results": [], + "params": { + "experience": null, + "projection": null, + "comfort": null, + "picture": null, + "sound": null, + "versionDetail": null, + "page": 1 + }, + "facets": [ + { + "__typename": "Facets", + "name": "showtimes.projection", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.experience", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.comfort", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.version_detail", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.picture", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.sound", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.service", + "values": [] + } + ], + "pagination": { + "page": 1, + "totalPages": 0, + "itemsPerPage": 15, + "totalItems": 0 + }, + "data": null + } + } + } + ] +} diff --git a/tests/test_allocine.py b/tests/test_allocine.py index 1e26735..21ad78d 100644 --- a/tests/test_allocine.py +++ b/tests/test_allocine.py @@ -91,6 +91,20 @@ def test_get_showtimes_treats_not_found_as_empty_without_retry(allocine: Allocin assert allocine.get_showtimes("C0125", from_date=date(2026, 9, 29)) == [] +@freeze_time("2026-09-29") +def test_get_showtimes_stops_after_no_showtime_error(allocine: Allocine, json_snapshot, allocine_vcr): + showtimes = allocine.get_showtimes("W0746", to_date=date(2026, 10, 5)) + + assert [asdict(showtime) for showtime in showtimes] == json_snapshot + + +@freeze_time("2026-09-26") +def test_get_showtimes_skips_to_next_showtime_date(allocine: Allocine, json_snapshot, allocine_vcr): + showtimes = allocine.get_showtimes("W1021", to_date=date(2026, 10, 2)) + + assert [asdict(showtime) for showtime in showtimes] == json_snapshot + + def test_get_showtimes_rejects_inverted_date_range( allocine, ): From f6b12f37d4b0d119bdce491720e14cdf9093969c Mon Sep 17 00:00:00 2001 From: Thibault Ducret Date: Tue, 29 Sep 2026 16:35:17 +0200 Subject: [PATCH 2/2] Resolve theaters without current showtimes --- allocine/client.py | 5 +- ...get_theater_without_current_showtimes.json | 7 + ...get_theater_without_current_showtimes.json | 200 ++++++++++++++++++ tests/test_allocine.py | 6 + 4 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 tests/__snapshots__/test_allocine/test_get_theater_without_current_showtimes.json create mode 100644 tests/cassettes/get_theater_without_current_showtimes.json diff --git a/allocine/client.py b/allocine/client.py index 6726702..bb9596d 100644 --- a/allocine/client.py +++ b/allocine/client.py @@ -132,7 +132,10 @@ def __init__( def get_theater(self, theater_id: str) -> Theater: resp = self._client.get_showtimelist_by_theater_id(theater_id=theater_id) if not resp.get("results"): - raise ValueError(f"Theater not found. Is theater id {theater_id!r} correct?") + try: + return self._get_theater_details_from_page(theater_id) + except ValueError: + raise ValueError(f"Theater not found. Is theater id {theater_id!r} correct?") from None movie_id: int | None = jmespath.search("results[0].movie.internalId", resp) assert movie_id is not None, "We need at least one showtime to get details about a theater" diff --git a/tests/__snapshots__/test_allocine/test_get_theater_without_current_showtimes.json b/tests/__snapshots__/test_allocine/test_get_theater_without_current_showtimes.json new file mode 100644 index 0000000..684fb33 --- /dev/null +++ b/tests/__snapshots__/test_allocine/test_get_theater_without_current_showtimes.json @@ -0,0 +1,7 @@ +{ + "address": "route de Troyes", + "city": "Chaource", + "name": "Ciné Ligue CA - Chaource (ex Collège A.Jamyn)", + "theater_id": "W1021", + "zipcode": "10210" +} diff --git a/tests/cassettes/get_theater_without_current_showtimes.json b/tests/cassettes/get_theater_without_current_showtimes.json new file mode 100644 index 0000000..e3b55e9 --- /dev/null +++ b/tests/cassettes/get_theater_without_current_showtimes.json @@ -0,0 +1,200 @@ +{ + "version": 1, + "interactions": [ + { + "request": { + "method": "GET", + "uri": "https://www.allocine.fr/_/showtimes/theater-W1021/", + "body": null, + "headers": {} + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "date": [ + "Tue, 29 Sep 2026 14:33:10 GMT" + ], + "content-type": [ + "application/json" + ], + "cache-control": [ + "max-age=3600, public, stale-if-error=172800, stale-while-revalidate=86400" + ], + "vary": [ + "Origin, accept-encoding" + ], + "server": [ + "cloudflare" + ], + "via": [ + "1.1 google" + ], + "alt-svc": [ + "h3=\":443\"; ma=86400" + ], + "age": [ + "689" + ], + "last-modified": [ + "Tue, 29 Sep 2026 14:21:41 GMT" + ], + "cf-cache-status": [ + "HIT" + ], + "server-timing": [ + "cfEdge;dur=19,cfOrigin;dur=0" + ], + "report-to": [ + "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=Oh24iIRBvhf%2BvbnfaJOT6GEa789kC1xxx9ceIfKLRupOh1rmeYmjGesqNKBZyi8j65A3XNNpmiuHkW4qU4fNVeIk7NNK7xPVzU9hwm9sf88VlqjBhDHXNh07TusMhwkQig%3D%3D\"}]}" + ], + "nel": [ + "{\"report_to\":\"cf-nel\",\"success_fraction\":0.01,\"max_age\":604800}" + ], + "cf-ray": [ + "a42bb3907a2b9e90-CDG" + ] + }, + "body": { + "error": true, + "message": "next.showtime.on", + "messageParam": null, + "nextDate": "2026-10-02", + "results": [], + "params": { + "experience": null, + "projection": null, + "comfort": null, + "picture": null, + "sound": null, + "versionDetail": null, + "page": 1 + }, + "facets": [ + { + "__typename": "Facets", + "name": "showtimes.projection", + "values": [ + { + "key": "Digital", + "translation": { + "tag": "DIGITAL", + "name": "Numérique" + } + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.experience", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.comfort", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.version_detail", + "values": [ + { + "key": "Local", + "versionDetail": "LOCAL" + } + ] + }, + { + "__typename": "Facets", + "name": "showtimes.picture", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.sound", + "values": [] + }, + { + "__typename": "Facets", + "name": "showtimes.service", + "values": [] + } + ], + "pagination": { + "page": 1, + "totalPages": 0, + "itemsPerPage": 15, + "totalItems": 0 + }, + "data": null + } + } + }, + { + "request": { + "method": "GET", + "uri": "https://www.allocine.fr/seance/salle_gen_csalle=W1021.html", + "body": null, + "headers": {} + }, + "response": { + "status": { + "code": 200, + "message": "OK" + }, + "headers": { + "date": [ + "Tue, 29 Sep 2026 14:33:10 GMT" + ], + "content-type": [ + "text/html; charset=UTF-8" + ], + "cache-control": [ + "max-age=3600, public, stale-if-error=172800, stale-while-revalidate=86400" + ], + "link": [ + "; rel=\"preload\"; as=\"font\"; type=\"font/woff2\",; rel=\"preload\"; as=\"image\"; type=\"image/svg+xml\",; rel=\"preload\"; as=\"script\",; rel=\"preload\"; as=\"script\"" + ], + "vary": [ + "Accept-Encoding,Origin" + ], + "nel": [ + "{\"report_to\":\"cf-nel\",\"success_fraction\":0.01,\"max_age\":604800}" + ], + "server": [ + "cloudflare" + ], + "via": [ + "1.1 google" + ], + "alt-svc": [ + "h3=\":443\"; ma=86400" + ], + "age": [ + "638" + ], + "last-modified": [ + "Tue, 29 Sep 2026 14:22:32 GMT" + ], + "cf-cache-status": [ + "HIT" + ], + "server-timing": [ + "cfEdge;dur=10,cfOrigin;dur=0" + ], + "report-to": [ + "{\"group\":\"cf-nel\",\"max_age\":604800,\"endpoints\":[{\"url\":\"https://a.nel.cloudflare.com/report/v4?s=fFLgv4n5VTsXeF%2BaG%2BtdeUDmd5zSPRTfhiqnZsGiRECjnIhUPRjDqz%2BjyOWdOCYpA0dtAdSleSxM1ZNrcKPrIZUQk0p%2BKA8M8wPRyBzB0Xv1ZV1%2BfR8RVvaq2Y%2FZo4B9sw%3D%3D\"}]}" + ], + "cf-ray": [ + "a42bb390fa5dd031-CDG" + ] + }, + "body": { + "string": "\n\n\n \n \n \n \n Cinéma Ciné Ligue CA - Chaource (ex Collège A.Jamyn) à Chaource (10210 ) - Achat ticket cinéma disponible - AlloCiné\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n\n \n \n \n\n \n\n \n \n \n \n \n \n \n\n\n\n\n\n\n
\n
\n\n \n\n \n \n \n \n\n\n\n\n\n
\n
\n
\n menu\n close2\n
\n\n \n \n \n \n
\n
\n \n
\n \n profil\n
\n
\n\n
\n
    \n
\n
\n
\n \n search\n
\n
\n\n \n\n\n \n
\n
Annuler
\n\n \n
\n
\n \n
\n
\n \n
\n
\n\n ok \n
\n
\n\n
\n Ex:\n\n L'odyssée, Deepwater, La Fille Inconnue, Overdrive\n\n
\n
\n \n \n\n \n \n \n
\n
\n
\n \n
\n \n \n\n\n\n\n
\n AccueilCinémaSéances et horairesCinéma Aube

\n Ciné Ligue CA - Chaource (ex Collège A.Jamyn) à Chaource

\n\n
\n\n
\n
\n
\n \n \n \n \n \n \n \n \n \n
\n \n
\n \n \n \"Ciné\n \n \n\n \n \n \n\n
\n
\n\n
\n
\n \n
Ciné Ligue CA - Chaource (ex Collège A.Jamyn)
\n \n
route de Troyes 10210 Chaource
\n\n \n \n
\n \n
\n\n
\n
\n\n
\n
\n 1 Salle\n
\n
\n \n \n \n
\n\n
\n
Cartes acceptées :
\n
\n
Chèque Cinéma Universel
\n
Cinéchèques
\n
\n
\n
\n
\n\n
\n\n
\n \n\n \n \n\n\n
\n\n \n \n\n\n\n\n
\n
\n \n
\n\n\n \n \n \n \n \n
\n\n \n
mar.
29
sept.
mer.
30
sept.
jeu.
1
oct.
ven.
2
oct.
sam.
3
oct.
dim.
4
oct.
lun.
5
oct.
mar.
6
oct.
mer.
7
oct.
jeu.
8
oct.
ven.
9
oct.
sam.
10
oct.
dim.
11
oct.
lun.
12
oct.
mar.
13
oct.
mer.
14
oct.
jeu.
15
oct.
ven.
16
oct.
sam.
17
oct.
dim.
18
oct.
lun.
19
oct.
mar.
20
oct.
mer.
21
oct.
jeu.
22
oct.
ven.
23
oct.
sam.
24
oct.
dim.
25
oct.
lun.
26
oct.
\n
\n
\n
\n\n
\n \n
\n
\n\n \n\n\n\n\n \n\n \n \n \n \n \n
\n \n
\n \n \n \n \n \n \n \n \n \n
\n \n
\n \n \n \"L'île\n \n \n \n\n\n\n \n \n \n\n
\n
\n\n
\n
\n
L'île des souvenirs
\n
Ça sort le mercredi 21 octobre au ciné !
\n
\n
\n
\n X\n
\n
\n\n \n\n\n \n \n \n \n \n \n \n\n\n\n \n \n \n \n \n
\n
\n \n \n
\n \n \n\n\n
\n \n\n \n
\n \n \n \n \n \n
\n\n
\n \n \n
\n
\n \n \n Back to Top\n\n \n
 
\n\n
\n \n
\n \n \n\n\n\n\n\n \n\n\n \n \n\n \n\n\n \n\n \n \n \n \n \n\n \n\n \n
\n\n \n\n\n \n \n \n \n\n\n
\n\n\n\n\n" + } + } + } + ] +} diff --git a/tests/test_allocine.py b/tests/test_allocine.py index 21ad78d..85e7195 100644 --- a/tests/test_allocine.py +++ b/tests/test_allocine.py @@ -20,6 +20,12 @@ def test_get_theater(allocine: Allocine, json_snapshot, allocine_vcr): assert asdict(theater) == json_snapshot +def test_get_theater_without_current_showtimes(allocine: Allocine, json_snapshot, allocine_vcr): + theater = allocine.get_theater("W1021") + + assert asdict(theater) == json_snapshot + + def test_search_theaters(allocine: Allocine, json_snapshot, allocine_vcr): theaters = allocine.search_theaters(geocode=83178)