allocine 0.1.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- allocine/__init__.py +56 -0
- allocine/api.py +132 -0
- allocine/cache.py +134 -0
- allocine/cli.py +159 -0
- allocine/client.py +241 -0
- allocine/models.py +233 -0
- allocine/nationalities.py +448 -0
- allocine/schedules.py +162 -0
- allocine-0.1.0a0.dist-info/METADATA +214 -0
- allocine-0.1.0a0.dist-info/RECORD +14 -0
- allocine-0.1.0a0.dist-info/WHEEL +5 -0
- allocine-0.1.0a0.dist-info/entry_points.txt +2 -0
- allocine-0.1.0a0.dist-info/licenses/LICENSE +22 -0
- allocine-0.1.0a0.dist-info/top_level.txt +1 -0
allocine/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Top-level package for Allociné."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version as _distribution_version
|
|
4
|
+
|
|
5
|
+
from allocine.api import AllocineApi
|
|
6
|
+
from allocine.client import DEFAULT_DATE_FORMAT, Allocine
|
|
7
|
+
from allocine.models import (
|
|
8
|
+
Movie,
|
|
9
|
+
MovieVersion,
|
|
10
|
+
Schedule,
|
|
11
|
+
Showtime,
|
|
12
|
+
Theater,
|
|
13
|
+
day_str,
|
|
14
|
+
get_french_month,
|
|
15
|
+
get_hour_short_str,
|
|
16
|
+
short_day_str,
|
|
17
|
+
to_french_short_weekday,
|
|
18
|
+
to_french_weekday,
|
|
19
|
+
)
|
|
20
|
+
from allocine.schedules import (
|
|
21
|
+
build_program_str,
|
|
22
|
+
build_weekly_schedule_str,
|
|
23
|
+
check_schedules_within_week,
|
|
24
|
+
create_weekdays_str,
|
|
25
|
+
get_available_dates,
|
|
26
|
+
get_showtimes_of_a_day,
|
|
27
|
+
group_showtimes_per_schedule,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__author__ = "Thibault Ducret"
|
|
31
|
+
__email__ = "hello@tducret.com"
|
|
32
|
+
__version__ = _distribution_version("allocine")
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Allocine",
|
|
36
|
+
"AllocineApi",
|
|
37
|
+
"DEFAULT_DATE_FORMAT",
|
|
38
|
+
"Movie",
|
|
39
|
+
"MovieVersion",
|
|
40
|
+
"Schedule",
|
|
41
|
+
"Showtime",
|
|
42
|
+
"Theater",
|
|
43
|
+
"build_program_str",
|
|
44
|
+
"build_weekly_schedule_str",
|
|
45
|
+
"check_schedules_within_week",
|
|
46
|
+
"create_weekdays_str",
|
|
47
|
+
"day_str",
|
|
48
|
+
"get_available_dates",
|
|
49
|
+
"get_french_month",
|
|
50
|
+
"get_hour_short_str",
|
|
51
|
+
"get_showtimes_of_a_day",
|
|
52
|
+
"group_showtimes_per_schedule",
|
|
53
|
+
"short_day_str",
|
|
54
|
+
"to_french_short_weekday",
|
|
55
|
+
"to_french_weekday",
|
|
56
|
+
]
|
allocine/api.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from datetime import date as Date
|
|
2
|
+
from os import PathLike
|
|
3
|
+
|
|
4
|
+
import backoff
|
|
5
|
+
import httpx2
|
|
6
|
+
|
|
7
|
+
from allocine.cache import CACHE_MISS, CachedResponse, CacheOption, HttpCache
|
|
8
|
+
|
|
9
|
+
SHOWTIMES_BASE_URL = "https://www.allocine.fr/_/showtimes"
|
|
10
|
+
THEATERS_BASE_URL = "https://www.allocine.fr/salle/cinema"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AllocineApi:
|
|
14
|
+
"""Client to process requests with the Allocine APIs."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
*,
|
|
19
|
+
cache: CacheOption = False,
|
|
20
|
+
cache_dir: str | PathLike[str] | None = None,
|
|
21
|
+
):
|
|
22
|
+
headers = {
|
|
23
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; \
|
|
24
|
+
Intel Mac OS X 10.14; rv:63.0) \
|
|
25
|
+
Gecko/20100101 Firefox/63.0",
|
|
26
|
+
}
|
|
27
|
+
self.session = httpx2.Client(headers=headers, timeout=30)
|
|
28
|
+
self.cache = HttpCache(cache, cache_dir)
|
|
29
|
+
|
|
30
|
+
def get_showtimelist_by_theater_id(
|
|
31
|
+
self,
|
|
32
|
+
theater_id: str,
|
|
33
|
+
page: int | None = None,
|
|
34
|
+
date: Date | None = None,
|
|
35
|
+
):
|
|
36
|
+
url = f"{SHOWTIMES_BASE_URL}/theater-{theater_id}/"
|
|
37
|
+
if date:
|
|
38
|
+
url += f"d-{date.isoformat()}/"
|
|
39
|
+
if page:
|
|
40
|
+
url += f"p-{page}/"
|
|
41
|
+
|
|
42
|
+
response = self._request(url, 200, not_found_ok=True)
|
|
43
|
+
if response.status_code == 404:
|
|
44
|
+
return {"results": []}
|
|
45
|
+
return response.json()
|
|
46
|
+
|
|
47
|
+
def get_showtimes_by_movie_and_theater_id(
|
|
48
|
+
self,
|
|
49
|
+
movie_id: int,
|
|
50
|
+
theater_id: str,
|
|
51
|
+
):
|
|
52
|
+
url = f"{SHOWTIMES_BASE_URL}/ope/movie-{movie_id}/theater-{theater_id}/"
|
|
53
|
+
return self._get(url)
|
|
54
|
+
|
|
55
|
+
def get_theater_page(self, theater_id: str) -> str:
|
|
56
|
+
url = f"https://www.allocine.fr/seance/salle_gen_csalle={theater_id}.html"
|
|
57
|
+
return self._get_text(url)
|
|
58
|
+
|
|
59
|
+
def get_theaterlist_by_geocode(self, geocode: int | str, page: int = 1) -> str:
|
|
60
|
+
url = f"{THEATERS_BASE_URL}/ville-{geocode}/"
|
|
61
|
+
params = {"page": page} if page > 1 else None
|
|
62
|
+
return self._get_text(url, params=params)
|
|
63
|
+
|
|
64
|
+
def clear_cache(self) -> int:
|
|
65
|
+
return self.cache.clear()
|
|
66
|
+
|
|
67
|
+
def close(self) -> None:
|
|
68
|
+
self.session.close()
|
|
69
|
+
self.cache.close()
|
|
70
|
+
|
|
71
|
+
def __enter__(self):
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
75
|
+
self.close()
|
|
76
|
+
|
|
77
|
+
def _get(self, url: str, expected_status: int = 200, *args, **kwargs) -> dict:
|
|
78
|
+
return self._request(url, expected_status, *args, **kwargs).json()
|
|
79
|
+
|
|
80
|
+
def _get_text(self, url: str, expected_status: int = 200, *args, **kwargs) -> str:
|
|
81
|
+
return self._request(url, expected_status, *args, **kwargs).text
|
|
82
|
+
|
|
83
|
+
def _request(
|
|
84
|
+
self,
|
|
85
|
+
url: str,
|
|
86
|
+
expected_status: int,
|
|
87
|
+
*args,
|
|
88
|
+
not_found_ok: bool = False,
|
|
89
|
+
**kwargs,
|
|
90
|
+
) -> httpx2.Response:
|
|
91
|
+
params = kwargs.get("params")
|
|
92
|
+
cached = self.cache.get(url, params)
|
|
93
|
+
if cached is not CACHE_MISS and cached.is_fresh():
|
|
94
|
+
return cached.to_response()
|
|
95
|
+
|
|
96
|
+
if cached is not CACHE_MISS:
|
|
97
|
+
headers = dict(kwargs.get("headers") or {})
|
|
98
|
+
if etag := cached.headers.get("etag"):
|
|
99
|
+
headers["If-None-Match"] = etag
|
|
100
|
+
if last_modified := cached.headers.get("last-modified"):
|
|
101
|
+
headers["If-Modified-Since"] = last_modified
|
|
102
|
+
kwargs["headers"] = headers
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
response = self._fetch(url, expected_status, *args, not_found_ok=not_found_ok, **kwargs)
|
|
106
|
+
except (ValueError, httpx2.HTTPError):
|
|
107
|
+
if cached is not CACHE_MISS and cached.can_serve_on_error():
|
|
108
|
+
return cached.to_response()
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
if response.status_code == 304 and cached is not CACHE_MISS:
|
|
112
|
+
response = self._revalidated_response(cached, response)
|
|
113
|
+
self.cache.set(url, response, params)
|
|
114
|
+
return response
|
|
115
|
+
|
|
116
|
+
@backoff.on_exception(backoff.expo, ValueError, max_tries=5, max_time=30)
|
|
117
|
+
def _fetch(
|
|
118
|
+
self,
|
|
119
|
+
url: str,
|
|
120
|
+
expected_status: int,
|
|
121
|
+
*args,
|
|
122
|
+
not_found_ok: bool = False,
|
|
123
|
+
**kwargs,
|
|
124
|
+
) -> httpx2.Response:
|
|
125
|
+
ret = self.session.get(url, *args, **kwargs)
|
|
126
|
+
if ret.status_code not in {expected_status, 304} and not (not_found_ok and ret.status_code == 404):
|
|
127
|
+
raise ValueError("{!r} : expected status {}, received {}".format(url, expected_status, ret.status_code))
|
|
128
|
+
return ret
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _revalidated_response(cached: CachedResponse, response: httpx2.Response) -> httpx2.Response:
|
|
132
|
+
return cached.to_response(response.headers)
|
allocine/cache.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from time import time
|
|
5
|
+
from urllib.parse import urlencode
|
|
6
|
+
from weakref import finalize
|
|
7
|
+
|
|
8
|
+
import httpx2
|
|
9
|
+
from diskcache import Cache
|
|
10
|
+
from platformdirs import user_cache_path
|
|
11
|
+
|
|
12
|
+
CACHE_VERSION = 3
|
|
13
|
+
CACHE_MISS = object()
|
|
14
|
+
|
|
15
|
+
CacheOption = bool | Cache
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CachedResponse:
|
|
20
|
+
status_code: int
|
|
21
|
+
headers: dict[str, str]
|
|
22
|
+
content: bytes
|
|
23
|
+
stored_at: float
|
|
24
|
+
initial_age: int
|
|
25
|
+
max_age: int
|
|
26
|
+
stale_if_error: int
|
|
27
|
+
stale_while_revalidate: int
|
|
28
|
+
|
|
29
|
+
def age(self, now: float | None = None) -> float:
|
|
30
|
+
return self.initial_age + (time() if now is None else now) - self.stored_at
|
|
31
|
+
|
|
32
|
+
def is_fresh(self, now: float | None = None) -> bool:
|
|
33
|
+
return self.age(now) < self.max_age
|
|
34
|
+
|
|
35
|
+
def can_serve_on_error(self, now: float | None = None) -> bool:
|
|
36
|
+
return self.age(now) < self.max_age + self.stale_if_error
|
|
37
|
+
|
|
38
|
+
def retention(self) -> float:
|
|
39
|
+
stale = max(self.stale_if_error, self.stale_while_revalidate)
|
|
40
|
+
return self.max_age + stale - self.initial_age
|
|
41
|
+
|
|
42
|
+
def to_response(self, revalidation_headers: Mapping[str, str] | None = None) -> httpx2.Response:
|
|
43
|
+
headers = dict(self.headers)
|
|
44
|
+
if revalidation_headers is not None:
|
|
45
|
+
headers.pop("age", None)
|
|
46
|
+
headers.update(revalidation_headers)
|
|
47
|
+
return httpx2.Response(self.status_code, headers=_decoded_headers(headers), content=self.content)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class HttpCache:
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
cache: CacheOption = False,
|
|
54
|
+
cache_dir: str | PathLike[str] | None = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
if cache_dir is not None and cache is not True:
|
|
57
|
+
raise ValueError("cache_dir can only be used when cache=True")
|
|
58
|
+
|
|
59
|
+
self._owns_backend = cache is True
|
|
60
|
+
self._backend = (
|
|
61
|
+
Cache(cache_dir or user_cache_path("allocine")) if cache is True else None if cache is False else cache
|
|
62
|
+
)
|
|
63
|
+
self._finalizer = (
|
|
64
|
+
finalize(self, self._backend.close) if self._owns_backend and self._backend is not None else None
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get(self, url: str, params: dict | None = None):
|
|
68
|
+
if self._backend is None:
|
|
69
|
+
return CACHE_MISS
|
|
70
|
+
return self._backend.get(self._key(url, params), default=CACHE_MISS)
|
|
71
|
+
|
|
72
|
+
def set(self, url: str, response: httpx2.Response, params: dict | None = None) -> None:
|
|
73
|
+
if self._backend is None or (cached := _to_cached_response(response)) is None:
|
|
74
|
+
return
|
|
75
|
+
if (retention := cached.retention()) > 0:
|
|
76
|
+
self._backend.set(self._key(url, params), cached, expire=retention)
|
|
77
|
+
|
|
78
|
+
def clear(self) -> int:
|
|
79
|
+
return self._backend.clear() if self._backend is not None else 0
|
|
80
|
+
|
|
81
|
+
def close(self) -> None:
|
|
82
|
+
if self._finalizer is not None:
|
|
83
|
+
self._finalizer()
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _key(url: str, params: dict | None) -> str:
|
|
87
|
+
query = urlencode(sorted((params or {}).items()), doseq=True)
|
|
88
|
+
return f"http:v{CACHE_VERSION}:{url}?{query}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _to_cached_response(response: httpx2.Response) -> CachedResponse | None:
|
|
92
|
+
directives = _parse_cache_control(response.headers.get("cache-control", ""))
|
|
93
|
+
if "public" not in directives or "no-store" in directives or "private" in directives:
|
|
94
|
+
return None
|
|
95
|
+
if (max_age := _seconds(directives, "max-age")) is None:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
return CachedResponse(
|
|
99
|
+
status_code=response.status_code,
|
|
100
|
+
headers=_decoded_headers(response.headers),
|
|
101
|
+
content=response.content,
|
|
102
|
+
stored_at=time(),
|
|
103
|
+
initial_age=_header_seconds(response.headers.get("age")) or 0,
|
|
104
|
+
max_age=max_age,
|
|
105
|
+
stale_if_error=_seconds(directives, "stale-if-error") or 0,
|
|
106
|
+
stale_while_revalidate=_seconds(directives, "stale-while-revalidate") or 0,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _parse_cache_control(value: str) -> dict[str, str | None]:
|
|
111
|
+
directives = {}
|
|
112
|
+
for item in value.split(","):
|
|
113
|
+
name, separator, directive_value = item.strip().partition("=")
|
|
114
|
+
if name:
|
|
115
|
+
directives[name.lower()] = directive_value.strip('"') if separator else None
|
|
116
|
+
return directives
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _decoded_headers(headers: Mapping[str, str]) -> dict[str, str]:
|
|
120
|
+
decoded_headers = dict(headers)
|
|
121
|
+
for name in ("content-encoding", "content-length", "transfer-encoding"):
|
|
122
|
+
decoded_headers.pop(name, None)
|
|
123
|
+
return decoded_headers
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _seconds(directives: dict[str, str | None], name: str) -> int | None:
|
|
127
|
+
return _header_seconds(directives.get(name))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _header_seconds(value: str | None) -> int | None:
|
|
131
|
+
try:
|
|
132
|
+
return max(0, int(value)) if value is not None else None
|
|
133
|
+
except ValueError:
|
|
134
|
+
return None
|
allocine/cli.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Command-line interface for Allociné showtimes."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, timedelta
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from prettytable import ALL, FRAME, UNICODE, PrettyTable
|
|
7
|
+
|
|
8
|
+
from allocine.client import Allocine
|
|
9
|
+
from allocine.schedules import get_showtimes_of_a_day
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def clear_cache(ctx, param, value):
|
|
13
|
+
if not value or ctx.resilient_parsing:
|
|
14
|
+
return
|
|
15
|
+
with Allocine(cache=True) as allocine:
|
|
16
|
+
removed = allocine.clear_cache()
|
|
17
|
+
click.echo(f"Cache vidé ({removed} entrée(s)).")
|
|
18
|
+
ctx.exit()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def extract_field_names(dict_list):
|
|
22
|
+
"""Return a sorted list of field names from a dictionary list."""
|
|
23
|
+
field_names = []
|
|
24
|
+
for row_dict in dict_list:
|
|
25
|
+
field_names += row_dict.keys()
|
|
26
|
+
field_names = list(set(field_names))
|
|
27
|
+
return sorted(field_names)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@click.command()
|
|
31
|
+
@click.argument("id_cinema", type=str, required=True)
|
|
32
|
+
@click.option(
|
|
33
|
+
"--clear-cache",
|
|
34
|
+
is_flag=True,
|
|
35
|
+
is_eager=True,
|
|
36
|
+
expose_value=False,
|
|
37
|
+
callback=clear_cache,
|
|
38
|
+
help="vide entièrement le cache et quitte",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"--jour",
|
|
42
|
+
"-j",
|
|
43
|
+
type=str,
|
|
44
|
+
help="jour des séances souhaitées \
|
|
45
|
+
(au format DD/MM/YYYY ou +1 pour demain), par défaut : aujourd’hui",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--semaine",
|
|
49
|
+
"-s",
|
|
50
|
+
is_flag=True,
|
|
51
|
+
help="affiche les séance pour les 7 prochains jours",
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--entrelignes",
|
|
55
|
+
"-e",
|
|
56
|
+
is_flag=True,
|
|
57
|
+
help="ajoute une ligne entre chaque film pour améliorer la lisibilité",
|
|
58
|
+
)
|
|
59
|
+
def main(id_cinema, entrelignes, jour=None, semaine=None):
|
|
60
|
+
"""
|
|
61
|
+
Les séances de votre cinéma dans le terminal, avec
|
|
62
|
+
ID_CINEMA : identifiant du cinéma sur Allociné,
|
|
63
|
+
ex: C0159 pour l’UGC Ciné Cité Les Halles. Se trouve dans l’url :
|
|
64
|
+
http://allocine.fr/seance/salle_gen_csalle=<ID_CINEMA>.html
|
|
65
|
+
"""
|
|
66
|
+
today = date.today()
|
|
67
|
+
allocine = Allocine(cache=True)
|
|
68
|
+
|
|
69
|
+
jours = []
|
|
70
|
+
if semaine is False:
|
|
71
|
+
if jour is None:
|
|
72
|
+
jours.append(today.strftime("%d/%m/%Y"))
|
|
73
|
+
elif jour[0] == "+":
|
|
74
|
+
delta_jours = int(jour[1:])
|
|
75
|
+
jour_obj = today + timedelta(days=delta_jours)
|
|
76
|
+
jours.append(jour_obj.strftime("%d/%m/%Y"))
|
|
77
|
+
else:
|
|
78
|
+
jours.append(jour)
|
|
79
|
+
else:
|
|
80
|
+
for delta in range(0, 7):
|
|
81
|
+
jour_obj = today + timedelta(days=delta)
|
|
82
|
+
jours.append(jour_obj.strftime("%d/%m/%Y"))
|
|
83
|
+
|
|
84
|
+
requested_dates = [datetime.strptime(jour, "%d/%m/%Y").date() for jour in jours]
|
|
85
|
+
showtimes = allocine.get_showtimes(
|
|
86
|
+
theater_id=id_cinema,
|
|
87
|
+
from_date=min(requested_dates),
|
|
88
|
+
to_date=max(requested_dates),
|
|
89
|
+
)
|
|
90
|
+
try:
|
|
91
|
+
theater = allocine.get_theater(theater_id=id_cinema)
|
|
92
|
+
except ValueError as error:
|
|
93
|
+
raise click.ClickException(str(error)) from None
|
|
94
|
+
|
|
95
|
+
print("{}, le ".format(theater.name), end="")
|
|
96
|
+
for jour in jours:
|
|
97
|
+
print(get_showtime_table(showtimes=showtimes, entrelignes=entrelignes, jour=jour))
|
|
98
|
+
print()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_showtime_table(showtimes, entrelignes, jour):
|
|
102
|
+
showtime_table = []
|
|
103
|
+
|
|
104
|
+
date_obj = datetime.strptime(jour, "%d/%m/%Y").date()
|
|
105
|
+
day_showtimes = get_showtimes_of_a_day(showtimes, date=date_obj)
|
|
106
|
+
movies_available_today = set(showtime.movie for showtime in day_showtimes)
|
|
107
|
+
|
|
108
|
+
for movie_version in movies_available_today:
|
|
109
|
+
title = movie_version.title
|
|
110
|
+
if len(title) >= 31: # On tronque les titres trop longs
|
|
111
|
+
title = title[:31] + "..."
|
|
112
|
+
|
|
113
|
+
# '*1_film' pour être sûr que cela soit la 1ère colonne
|
|
114
|
+
movie_row = {"*1_film": "{} ({}) - {}".format(title, movie_version.version, movie_version.duration_str)}
|
|
115
|
+
|
|
116
|
+
movie_row["*2_note"] = "{}*".format(movie_version.rating_str)
|
|
117
|
+
|
|
118
|
+
for showtime in day_showtimes:
|
|
119
|
+
if showtime.movie != movie_version:
|
|
120
|
+
continue
|
|
121
|
+
hour = showtime.hour_str.split(":")[0] # 11:15 => 11
|
|
122
|
+
movie_row[hour] = showtime.hour_str
|
|
123
|
+
|
|
124
|
+
showtime_table.append(movie_row)
|
|
125
|
+
|
|
126
|
+
seances = showtime_table
|
|
127
|
+
|
|
128
|
+
retour = "{}\n".format(jour)
|
|
129
|
+
|
|
130
|
+
if len(seances) <= 0:
|
|
131
|
+
retour += "Aucune séance"
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
table = PrettyTable()
|
|
135
|
+
table.set_style(UNICODE)
|
|
136
|
+
table.header = False
|
|
137
|
+
|
|
138
|
+
if entrelignes is True:
|
|
139
|
+
table.hrules = ALL
|
|
140
|
+
else:
|
|
141
|
+
table.hrules = FRAME
|
|
142
|
+
|
|
143
|
+
table.field_names = extract_field_names(seances)
|
|
144
|
+
|
|
145
|
+
for seances_film in seances:
|
|
146
|
+
row = []
|
|
147
|
+
for field_name in table.field_names:
|
|
148
|
+
row.append(seances_film.get(field_name, ""))
|
|
149
|
+
table.add_row(row)
|
|
150
|
+
|
|
151
|
+
table.align["*1_film"] = "l"
|
|
152
|
+
table.sortby = "*1_film"
|
|
153
|
+
retour += str(table)
|
|
154
|
+
|
|
155
|
+
return retour
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
main()
|