diff --git a/allocine/client.py b/allocine/client.py index ba07c4a..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" @@ -158,6 +161,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/__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_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/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 1e26735..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) @@ -91,6 +97,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, ):