viaggiatreno 0.1.2__tar.gz
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.
- viaggiatreno-0.1.2/.gitignore +13 -0
- viaggiatreno-0.1.2/PKG-INFO +57 -0
- viaggiatreno-0.1.2/README.md +42 -0
- viaggiatreno-0.1.2/pyproject.toml +38 -0
- viaggiatreno-0.1.2/src/viaggiatreno/__init__.py +85 -0
- viaggiatreno-0.1.2/src/viaggiatreno/_version.py +24 -0
- viaggiatreno-0.1.2/src/viaggiatreno/api/__init__.py +22 -0
- viaggiatreno-0.1.2/src/viaggiatreno/api/client.py +355 -0
- viaggiatreno-0.1.2/src/viaggiatreno/api/parsers.py +195 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/__init__.py +52 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/common.py +64 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/route.py +35 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/service.py +72 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/station.py +54 -0
- viaggiatreno-0.1.2/src/viaggiatreno/models/train.py +236 -0
- viaggiatreno-0.1.2/src/viaggiatreno/py.typed +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: viaggiatreno
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: ViaggiaTreno API Client - Core Library
|
|
5
|
+
Project-URL: Homepage, https://github.com/matteoredaelli/pyviaggiatreno
|
|
6
|
+
Project-URL: Documentation, https://github.com/matteoredaelli/pyviaggiatreno/blob/main/README.md
|
|
7
|
+
Project-URL: Repository, https://github.com/matteoredaelli/pyviaggiatreno.git
|
|
8
|
+
Project-URL: Issues, https://github.com/matteoredaelli/pyviaggiatreno/issues
|
|
9
|
+
Author-email: Matteo Redaelli <matteo.redaelli@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: pydantic>=2.13.5
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# viaggiatreno
|
|
17
|
+
|
|
18
|
+
Client Python per le API non ufficiali di [ViaggiaTreno](http://www.viaggiatreno.it),
|
|
19
|
+
il sistema informativo in tempo reale della rete ferroviaria italiana.
|
|
20
|
+
|
|
21
|
+
La libreria fornisce:
|
|
22
|
+
|
|
23
|
+
- **`ViaggiaTrenoClient`** — client HTTP asincrono (basato su `httpx`) per tutti
|
|
24
|
+
gli endpoint REST di ViaggiaTreno (stazioni, partenze/arrivi, andamento treni,
|
|
25
|
+
tratte, infomobilità, meteo, statistiche).
|
|
26
|
+
- **Modelli dati tipizzati** (pydantic): `StationDetail`, `TrainBoardItem`,
|
|
27
|
+
`TrainStatus`, `DettaglioTratta`, `Statistics`, e altri.
|
|
28
|
+
- **Parser** per le risposte non-JSON (RSS infomobilità, autocomplete testuali).
|
|
29
|
+
|
|
30
|
+
## Installazione
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install viaggiatreno
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Esempio
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
from viaggiatreno import ViaggiaTrenoClient
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def main() -> None:
|
|
44
|
+
async with ViaggiaTrenoClient() as client:
|
|
45
|
+
stazioni = await client.autocompleta_stazione("Milano")
|
|
46
|
+
for s in stazioni:
|
|
47
|
+
print(s)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
asyncio.run(main())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Licenza
|
|
54
|
+
|
|
55
|
+
Vedi il file `LICENSE` nel repository.
|
|
56
|
+
|
|
57
|
+
Repository: https://github.com/matteoredaelli/pyviaggiatreno
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# viaggiatreno
|
|
2
|
+
|
|
3
|
+
Client Python per le API non ufficiali di [ViaggiaTreno](http://www.viaggiatreno.it),
|
|
4
|
+
il sistema informativo in tempo reale della rete ferroviaria italiana.
|
|
5
|
+
|
|
6
|
+
La libreria fornisce:
|
|
7
|
+
|
|
8
|
+
- **`ViaggiaTrenoClient`** — client HTTP asincrono (basato su `httpx`) per tutti
|
|
9
|
+
gli endpoint REST di ViaggiaTreno (stazioni, partenze/arrivi, andamento treni,
|
|
10
|
+
tratte, infomobilità, meteo, statistiche).
|
|
11
|
+
- **Modelli dati tipizzati** (pydantic): `StationDetail`, `TrainBoardItem`,
|
|
12
|
+
`TrainStatus`, `DettaglioTratta`, `Statistics`, e altri.
|
|
13
|
+
- **Parser** per le risposte non-JSON (RSS infomobilità, autocomplete testuali).
|
|
14
|
+
|
|
15
|
+
## Installazione
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install viaggiatreno
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Esempio
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import asyncio
|
|
25
|
+
from viaggiatreno import ViaggiaTrenoClient
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def main() -> None:
|
|
29
|
+
async with ViaggiaTrenoClient() as client:
|
|
30
|
+
stazioni = await client.autocompleta_stazione("Milano")
|
|
31
|
+
for s in stazioni:
|
|
32
|
+
print(s)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
asyncio.run(main())
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Licenza
|
|
39
|
+
|
|
40
|
+
Vedi il file `LICENSE` nel repository.
|
|
41
|
+
|
|
42
|
+
Repository: https://github.com/matteoredaelli/pyviaggiatreno
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.32", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "viaggiatreno"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "ViaggiaTreno API Client - Core Library"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Matteo Redaelli", email = "matteo.redaelli@gmail.com" }
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.12"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.28.1",
|
|
17
|
+
"pydantic>=2.13.5",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/matteoredaelli/pyviaggiatreno"
|
|
22
|
+
Documentation = "https://github.com/matteoredaelli/pyviaggiatreno/blob/main/README.md"
|
|
23
|
+
Repository = "https://github.com/matteoredaelli/pyviaggiatreno.git"
|
|
24
|
+
Issues = "https://github.com/matteoredaelli/pyviaggiatreno/issues"
|
|
25
|
+
|
|
26
|
+
# Derive the version from Git tags (e.g. tag "v0.5.0" -> version "0.5.0").
|
|
27
|
+
# The repository root is two levels up from this package.
|
|
28
|
+
[tool.hatch.version]
|
|
29
|
+
source = "vcs"
|
|
30
|
+
raw-options = { root = "../.." }
|
|
31
|
+
|
|
32
|
+
# Write the resolved version to a generated module so it is importable at
|
|
33
|
+
# runtime (viaggiatreno.__version__) without re-inspecting Git.
|
|
34
|
+
[tool.hatch.build.hooks.vcs]
|
|
35
|
+
version-file = "src/viaggiatreno/_version.py"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/viaggiatreno"]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""ViaggiaTreno API Client - Core library.
|
|
2
|
+
|
|
3
|
+
Libreria per interrogare le API non ufficiali di ViaggiaTreno: client HTTP
|
|
4
|
+
asincrono, parser delle risposte e modelli dati tipizzati (pydantic).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
from viaggiatreno.api.client import ViaggiaTrenoClient, format_viaggiatreno_datetime
|
|
10
|
+
from viaggiatreno.api.parsers import (
|
|
11
|
+
parse_infomobilita_rss,
|
|
12
|
+
parse_infomobilita_rss_box,
|
|
13
|
+
parse_infomobilita_ticker,
|
|
14
|
+
parse_station_autocomplete,
|
|
15
|
+
parse_station_nts_autocomplete,
|
|
16
|
+
parse_train_autocomplete,
|
|
17
|
+
)
|
|
18
|
+
from viaggiatreno.models.common import (
|
|
19
|
+
CLIENT_COMPANIES,
|
|
20
|
+
REGIONS,
|
|
21
|
+
TRAIN_STATUS_DESCRIPTIONS,
|
|
22
|
+
ViaggiaTrenoBaseModel,
|
|
23
|
+
)
|
|
24
|
+
from viaggiatreno.models.route import DettaglioTratta, TrattaSegment
|
|
25
|
+
from viaggiatreno.models.service import (
|
|
26
|
+
InfomobilitaNews,
|
|
27
|
+
InfomobilitaNewsHeadline,
|
|
28
|
+
StationWeather,
|
|
29
|
+
Statistics,
|
|
30
|
+
)
|
|
31
|
+
from viaggiatreno.models.station import (
|
|
32
|
+
StationAutocompleteItem,
|
|
33
|
+
StationDetail,
|
|
34
|
+
StationNTSAutocompleteItem,
|
|
35
|
+
StationSearchResult,
|
|
36
|
+
)
|
|
37
|
+
from viaggiatreno.models.train import (
|
|
38
|
+
Fermata,
|
|
39
|
+
TrainAutocompleteItem,
|
|
40
|
+
TrainBoardItem,
|
|
41
|
+
TrainSearchResult,
|
|
42
|
+
TrainStatus,
|
|
43
|
+
TrattaCanvasItem,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Generated at build time by hatch-vcs from the Git tag.
|
|
48
|
+
from viaggiatreno._version import __version__ # type: ignore[import-not-found,unused-ignore]
|
|
49
|
+
except ImportError: # pragma: no cover - only when running from an unbuilt tree
|
|
50
|
+
try:
|
|
51
|
+
__version__ = version("viaggiatreno")
|
|
52
|
+
except PackageNotFoundError:
|
|
53
|
+
__version__ = "0.0.0.dev0"
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"CLIENT_COMPANIES",
|
|
57
|
+
"REGIONS",
|
|
58
|
+
"TRAIN_STATUS_DESCRIPTIONS",
|
|
59
|
+
"DettaglioTratta",
|
|
60
|
+
"Fermata",
|
|
61
|
+
"InfomobilitaNews",
|
|
62
|
+
"InfomobilitaNewsHeadline",
|
|
63
|
+
"StationAutocompleteItem",
|
|
64
|
+
"StationDetail",
|
|
65
|
+
"StationNTSAutocompleteItem",
|
|
66
|
+
"StationSearchResult",
|
|
67
|
+
"StationWeather",
|
|
68
|
+
"Statistics",
|
|
69
|
+
"TrainAutocompleteItem",
|
|
70
|
+
"TrainBoardItem",
|
|
71
|
+
"TrainSearchResult",
|
|
72
|
+
"TrainStatus",
|
|
73
|
+
"TrattaCanvasItem",
|
|
74
|
+
"TrattaSegment",
|
|
75
|
+
"ViaggiaTrenoBaseModel",
|
|
76
|
+
"ViaggiaTrenoClient",
|
|
77
|
+
"__version__",
|
|
78
|
+
"format_viaggiatreno_datetime",
|
|
79
|
+
"parse_infomobilita_rss",
|
|
80
|
+
"parse_infomobilita_rss_box",
|
|
81
|
+
"parse_infomobilita_ticker",
|
|
82
|
+
"parse_station_autocomplete",
|
|
83
|
+
"parse_station_nts_autocomplete",
|
|
84
|
+
"parse_train_autocomplete",
|
|
85
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Package API per la libreria ViaggiaTreno."""
|
|
2
|
+
|
|
3
|
+
from viaggiatreno.api.client import ViaggiaTrenoClient, format_viaggiatreno_datetime
|
|
4
|
+
from viaggiatreno.api.parsers import (
|
|
5
|
+
parse_infomobilita_rss,
|
|
6
|
+
parse_infomobilita_rss_box,
|
|
7
|
+
parse_infomobilita_ticker,
|
|
8
|
+
parse_station_autocomplete,
|
|
9
|
+
parse_station_nts_autocomplete,
|
|
10
|
+
parse_train_autocomplete,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ViaggiaTrenoClient",
|
|
15
|
+
"format_viaggiatreno_datetime",
|
|
16
|
+
"parse_infomobilita_rss",
|
|
17
|
+
"parse_infomobilita_rss_box",
|
|
18
|
+
"parse_infomobilita_ticker",
|
|
19
|
+
"parse_station_autocomplete",
|
|
20
|
+
"parse_station_nts_autocomplete",
|
|
21
|
+
"parse_train_autocomplete",
|
|
22
|
+
]
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""Client asincrono per le API di ViaggiaTreno."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import Self
|
|
7
|
+
from urllib.parse import quote
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from viaggiatreno.api.parsers import (
|
|
12
|
+
parse_infomobilita_rss,
|
|
13
|
+
parse_infomobilita_rss_box,
|
|
14
|
+
parse_infomobilita_ticker,
|
|
15
|
+
parse_station_autocomplete,
|
|
16
|
+
parse_station_nts_autocomplete,
|
|
17
|
+
parse_train_autocomplete,
|
|
18
|
+
)
|
|
19
|
+
from viaggiatreno.models.route import DettaglioTratta, TrattaSegment
|
|
20
|
+
from viaggiatreno.models.service import (
|
|
21
|
+
InfomobilitaNews,
|
|
22
|
+
InfomobilitaNewsHeadline,
|
|
23
|
+
StationWeather,
|
|
24
|
+
Statistics,
|
|
25
|
+
)
|
|
26
|
+
from viaggiatreno.models.station import (
|
|
27
|
+
StationAutocompleteItem,
|
|
28
|
+
StationDetail,
|
|
29
|
+
StationNTSAutocompleteItem,
|
|
30
|
+
StationSearchResult,
|
|
31
|
+
)
|
|
32
|
+
from viaggiatreno.models.train import (
|
|
33
|
+
TrainAutocompleteItem,
|
|
34
|
+
TrainBoardItem,
|
|
35
|
+
TrainSearchResult,
|
|
36
|
+
TrainStatus,
|
|
37
|
+
TrattaCanvasItem,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
DEFAULT_BASE_URL = "http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno"
|
|
41
|
+
DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def format_viaggiatreno_datetime(dt: datetime | str | None = None) -> str:
|
|
45
|
+
"""Formatta la data/ora nel formato atteso dagli endpoint partenze/arrivi.
|
|
46
|
+
|
|
47
|
+
Esempio formato ViaggiaTreno: Sun Oct 04 2026 08:12:27 GMT+0200 (URL-encoded).
|
|
48
|
+
"""
|
|
49
|
+
if dt is None:
|
|
50
|
+
now = datetime.now(UTC).astimezone()
|
|
51
|
+
formatted = now.strftime("%a %b %d %Y %H:%M:%S GMT%z")
|
|
52
|
+
elif isinstance(dt, datetime):
|
|
53
|
+
formatted = dt.strftime("%a %b %d %Y %H:%M:%S GMT%z")
|
|
54
|
+
else:
|
|
55
|
+
# Se è già stringa
|
|
56
|
+
formatted = dt
|
|
57
|
+
|
|
58
|
+
return quote(formatted)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ViaggiaTrenoClient:
|
|
62
|
+
"""Client asincrono per tutte le chiamate REST di ViaggiaTreno."""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
67
|
+
timeout: float = 20.0,
|
|
68
|
+
client: httpx.AsyncClient | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
self.base_url = base_url.rstrip("/")
|
|
71
|
+
self.timeout = timeout
|
|
72
|
+
self._external_client = client
|
|
73
|
+
self._client: httpx.AsyncClient | None = client
|
|
74
|
+
|
|
75
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
76
|
+
if self._client is None or self._client.is_closed:
|
|
77
|
+
self._client = httpx.AsyncClient(
|
|
78
|
+
timeout=self.timeout,
|
|
79
|
+
headers={"User-Agent": DEFAULT_USER_AGENT},
|
|
80
|
+
follow_redirects=True,
|
|
81
|
+
)
|
|
82
|
+
return self._client
|
|
83
|
+
|
|
84
|
+
async def close(self) -> None:
|
|
85
|
+
if self._client and not self._external_client and not self._client.is_closed:
|
|
86
|
+
await self._client.aclose()
|
|
87
|
+
|
|
88
|
+
async def __aenter__(self) -> Self:
|
|
89
|
+
await self._get_client()
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __aexit__(
|
|
93
|
+
self,
|
|
94
|
+
exc_type: type[BaseException] | None,
|
|
95
|
+
exc_val: BaseException | None,
|
|
96
|
+
exc_tb: TracebackType | None,
|
|
97
|
+
) -> None:
|
|
98
|
+
await self.close()
|
|
99
|
+
|
|
100
|
+
async def _request(self, path: str) -> httpx.Response:
|
|
101
|
+
url = f"{self.base_url}/{path.lstrip('/')}"
|
|
102
|
+
client = await self._get_client()
|
|
103
|
+
response = await client.get(url)
|
|
104
|
+
return response
|
|
105
|
+
|
|
106
|
+
# -------------------------------------------------------------------------
|
|
107
|
+
# STAZIONI
|
|
108
|
+
# -------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
async def autocompleta_stazione(self, prefisso: str) -> list[StationAutocompleteItem]:
|
|
111
|
+
"""Suggerimenti stazioni per prefisso (GET /autocompletaStazione/{prefisso})."""
|
|
112
|
+
r = await self._request(f"autocompletaStazione/{quote(prefisso)}")
|
|
113
|
+
if r.status_code == 204 or not r.text.strip():
|
|
114
|
+
return []
|
|
115
|
+
r.raise_for_status()
|
|
116
|
+
return parse_station_autocomplete(r.text)
|
|
117
|
+
|
|
118
|
+
async def autocompleta_stazione_imposta_viaggio(
|
|
119
|
+
self, prefisso: str
|
|
120
|
+
) -> list[StationAutocompleteItem]:
|
|
121
|
+
"""Suggerimenti stazioni per imposta viaggio (GET /autocompletaStazioneImpostaViaggio/{prefisso})."""
|
|
122
|
+
r = await self._request(f"autocompletaStazioneImpostaViaggio/{quote(prefisso)}")
|
|
123
|
+
if r.status_code == 204 or not r.text.strip():
|
|
124
|
+
return []
|
|
125
|
+
r.raise_for_status()
|
|
126
|
+
return parse_station_autocomplete(r.text)
|
|
127
|
+
|
|
128
|
+
async def autocompleta_stazione_nts(self, prefisso: str) -> list[StationNTSAutocompleteItem]:
|
|
129
|
+
"""Suggerimenti stazioni e posti tecnici con codice NTS RICS (GET /autocompletaStazioneNTS/{prefisso})."""
|
|
130
|
+
r = await self._request(f"autocompletaStazioneNTS/{quote(prefisso)}")
|
|
131
|
+
if r.status_code == 204 or not r.text.strip():
|
|
132
|
+
return []
|
|
133
|
+
r.raise_for_status()
|
|
134
|
+
return parse_station_nts_autocomplete(r.text)
|
|
135
|
+
|
|
136
|
+
async def cerca_stazione(self, prefisso: str) -> list[StationSearchResult]:
|
|
137
|
+
"""Ricerca stazioni con risposta JSON strutturata (GET /cercaStazione/{prefisso})."""
|
|
138
|
+
r = await self._request(f"cercaStazione/{quote(prefisso)}")
|
|
139
|
+
if r.status_code == 204 or not r.text.strip():
|
|
140
|
+
return []
|
|
141
|
+
r.raise_for_status()
|
|
142
|
+
data = r.json()
|
|
143
|
+
return [StationSearchResult.model_validate(item) for item in data]
|
|
144
|
+
|
|
145
|
+
async def regione(self, codice_stazione: str) -> int | None:
|
|
146
|
+
"""Codice regione numerico di una stazione (GET /regione/{codiceStazione})."""
|
|
147
|
+
r = await self._request(f"regione/{quote(codice_stazione)}")
|
|
148
|
+
if r.status_code == 204 or not r.text.strip():
|
|
149
|
+
return None
|
|
150
|
+
r.raise_for_status()
|
|
151
|
+
try:
|
|
152
|
+
return int(r.text.strip())
|
|
153
|
+
except ValueError:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
async def dettaglio_stazione(
|
|
157
|
+
self, codice_stazione: str, codice_regione: int
|
|
158
|
+
) -> StationDetail | None:
|
|
159
|
+
"""Dettagli e coordinate di una stazione (GET /dettaglioStazione/{codiceStazione}/{codiceRegione})."""
|
|
160
|
+
r = await self._request(f"dettaglioStazione/{quote(codice_stazione)}/{codice_regione}")
|
|
161
|
+
if r.status_code == 204 or not r.text.strip():
|
|
162
|
+
return None
|
|
163
|
+
r.raise_for_status()
|
|
164
|
+
data = r.json()
|
|
165
|
+
return StationDetail.model_validate(data)
|
|
166
|
+
|
|
167
|
+
async def elenco_stazioni(self, codice_regione: int) -> list[StationDetail]:
|
|
168
|
+
"""Elenco completo stazioni di una regione (GET /elencoStazioni/{codiceRegione})."""
|
|
169
|
+
r = await self._request(f"elencoStazioni/{codice_regione}")
|
|
170
|
+
if r.status_code == 204 or not r.text.strip():
|
|
171
|
+
return []
|
|
172
|
+
r.raise_for_status()
|
|
173
|
+
data = r.json()
|
|
174
|
+
return [StationDetail.model_validate(item) for item in data]
|
|
175
|
+
|
|
176
|
+
# -------------------------------------------------------------------------
|
|
177
|
+
# PARTENZE E ARRIVI
|
|
178
|
+
# -------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
async def partenze(
|
|
181
|
+
self,
|
|
182
|
+
codice_stazione: str,
|
|
183
|
+
data_ora: datetime | str | None = None,
|
|
184
|
+
) -> list[TrainBoardItem]:
|
|
185
|
+
"""Tabellone partenze da una stazione (GET /partenze/{codiceStazione}/{dataOra})."""
|
|
186
|
+
encoded_date = format_viaggiatreno_datetime(data_ora)
|
|
187
|
+
r = await self._request(f"partenze/{quote(codice_stazione)}/{encoded_date}")
|
|
188
|
+
if r.status_code == 204 or not r.text.strip():
|
|
189
|
+
return []
|
|
190
|
+
r.raise_for_status()
|
|
191
|
+
data = r.json()
|
|
192
|
+
return [TrainBoardItem.model_validate(item) for item in data]
|
|
193
|
+
|
|
194
|
+
async def arrivi(
|
|
195
|
+
self,
|
|
196
|
+
codice_stazione: str,
|
|
197
|
+
data_ora: datetime | str | None = None,
|
|
198
|
+
) -> list[TrainBoardItem]:
|
|
199
|
+
"""Tabellone arrivi in una stazione (GET /arrivi/{codiceStazione}/{dataOra})."""
|
|
200
|
+
encoded_date = format_viaggiatreno_datetime(data_ora)
|
|
201
|
+
r = await self._request(f"arrivi/{quote(codice_stazione)}/{encoded_date}")
|
|
202
|
+
if r.status_code == 204 or not r.text.strip():
|
|
203
|
+
return []
|
|
204
|
+
r.raise_for_status()
|
|
205
|
+
data = r.json()
|
|
206
|
+
return [TrainBoardItem.model_validate(item) for item in data]
|
|
207
|
+
|
|
208
|
+
# -------------------------------------------------------------------------
|
|
209
|
+
# TRENI
|
|
210
|
+
# -------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
async def cerca_numero_treno_autocomplete(
|
|
213
|
+
self, numero_treno: int | str
|
|
214
|
+
) -> list[TrainAutocompleteItem]:
|
|
215
|
+
"""Corse attive per un numero treno (GET /cercaNumeroTrenoTrenoAutocomplete/{numeroTreno})."""
|
|
216
|
+
r = await self._request(f"cercaNumeroTrenoTrenoAutocomplete/{numero_treno}")
|
|
217
|
+
if r.status_code == 204 or not r.text.strip():
|
|
218
|
+
return []
|
|
219
|
+
r.raise_for_status()
|
|
220
|
+
return parse_train_autocomplete(r.text)
|
|
221
|
+
|
|
222
|
+
async def cerca_numero_treno(self, numero_treno: int | str) -> TrainSearchResult | None:
|
|
223
|
+
"""Identificativi corsa corrente per un treno in JSON (GET /cercaNumeroTreno/{numeroTreno})."""
|
|
224
|
+
r = await self._request(f"cercaNumeroTreno/{numero_treno}")
|
|
225
|
+
if r.status_code == 204 or not r.text.strip():
|
|
226
|
+
return None
|
|
227
|
+
r.raise_for_status()
|
|
228
|
+
data = r.json()
|
|
229
|
+
return TrainSearchResult.model_validate(data)
|
|
230
|
+
|
|
231
|
+
async def andamento_treno(
|
|
232
|
+
self,
|
|
233
|
+
codice_stazione_origine: str,
|
|
234
|
+
numero_treno: int | str,
|
|
235
|
+
data_partenza: int | str,
|
|
236
|
+
) -> TrainStatus | None:
|
|
237
|
+
"""Stato dettagliato treno e fermate (GET /andamentoTreno/{codiceStazioneOrigine}/{numeroTreno}/{dataPartenza})."""
|
|
238
|
+
r = await self._request(
|
|
239
|
+
f"andamentoTreno/{quote(codice_stazione_origine)}/{numero_treno}/{data_partenza}"
|
|
240
|
+
)
|
|
241
|
+
if r.status_code == 204 or not r.text.strip():
|
|
242
|
+
return None
|
|
243
|
+
r.raise_for_status()
|
|
244
|
+
data = r.json()
|
|
245
|
+
return TrainStatus.model_validate(data)
|
|
246
|
+
|
|
247
|
+
async def tratte_canvas(
|
|
248
|
+
self,
|
|
249
|
+
codice_stazione_origine: str,
|
|
250
|
+
numero_treno: int | str,
|
|
251
|
+
data_partenza: int | str,
|
|
252
|
+
) -> list[TrattaCanvasItem]:
|
|
253
|
+
"""Itinerario visuale ad albero (GET /tratteCanvas/{codiceStazioneOrigine}/{numeroTreno}/{dataPartenza})."""
|
|
254
|
+
r = await self._request(
|
|
255
|
+
f"tratteCanvas/{quote(codice_stazione_origine)}/{numero_treno}/{data_partenza}"
|
|
256
|
+
)
|
|
257
|
+
if r.status_code == 204 or not r.text.strip():
|
|
258
|
+
return []
|
|
259
|
+
r.raise_for_status()
|
|
260
|
+
data = r.json()
|
|
261
|
+
return [TrattaCanvasItem.model_validate(item) for item in data]
|
|
262
|
+
|
|
263
|
+
# -------------------------------------------------------------------------
|
|
264
|
+
# TRATTE E RETE
|
|
265
|
+
# -------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
async def elenco_tratte(
|
|
268
|
+
self,
|
|
269
|
+
categorie: str = "ES*,IC,EXP,EC,EN,REG",
|
|
270
|
+
timestamp: int | None = None,
|
|
271
|
+
) -> list[TrattaSegment]:
|
|
272
|
+
"""Segmenti della rete e occupazione da treni (GET /elencoTratte/0/6/{categorie}/null/{timestamp})."""
|
|
273
|
+
ts = timestamp if timestamp is not None else int(time.time() * 1000)
|
|
274
|
+
r = await self._request(f"elencoTratte/0/6/{quote(categorie)}/null/{ts}")
|
|
275
|
+
if r.status_code == 204 or not r.text.strip():
|
|
276
|
+
return []
|
|
277
|
+
r.raise_for_status()
|
|
278
|
+
data = r.json()
|
|
279
|
+
return [TrattaSegment.model_validate(item) for item in data]
|
|
280
|
+
|
|
281
|
+
async def dettagli_tratta(
|
|
282
|
+
self,
|
|
283
|
+
tratta_ab: int,
|
|
284
|
+
tratta_ba: int,
|
|
285
|
+
categorie: str = "ES*,IC,EXP,EC,EN,REG",
|
|
286
|
+
) -> list[DettaglioTratta]:
|
|
287
|
+
"""Treni presenti su un segmento (GET /dettagliTratta/0/{trattaAB}/{trattaBA}/{categorie}/null)."""
|
|
288
|
+
r = await self._request(f"dettagliTratta/0/{tratta_ab}/{tratta_ba}/{quote(categorie)}/null")
|
|
289
|
+
if r.status_code == 204 or not r.text.strip():
|
|
290
|
+
return []
|
|
291
|
+
r.raise_for_status()
|
|
292
|
+
data = r.json()
|
|
293
|
+
return [DettaglioTratta.model_validate(item) for item in data]
|
|
294
|
+
|
|
295
|
+
# -------------------------------------------------------------------------
|
|
296
|
+
# INFORMAZIONI DI SERVIZIO
|
|
297
|
+
# -------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
async def statistiche(self, timestamp: int | None = None) -> Statistics | None:
|
|
300
|
+
"""Statistiche treni circolanti e programmati (GET /statistiche/{timestamp})."""
|
|
301
|
+
ts = timestamp if timestamp is not None else int(time.time() * 1000)
|
|
302
|
+
r = await self._request(f"statistiche/{ts}")
|
|
303
|
+
if r.status_code == 204 or not r.text.strip():
|
|
304
|
+
return None
|
|
305
|
+
r.raise_for_status()
|
|
306
|
+
data = r.json()
|
|
307
|
+
return Statistics.model_validate(data)
|
|
308
|
+
|
|
309
|
+
async def datimeteo(self, codice_regione: int) -> dict[str, StationWeather]:
|
|
310
|
+
"""Previsioni meteo per stazioni di una regione (GET /datimeteo/{codiceRegione})."""
|
|
311
|
+
r = await self._request(f"datimeteo/{codice_regione}")
|
|
312
|
+
if r.status_code == 204 or not r.text.strip():
|
|
313
|
+
return {}
|
|
314
|
+
r.raise_for_status()
|
|
315
|
+
data = r.json()
|
|
316
|
+
results: dict[str, StationWeather] = {}
|
|
317
|
+
for station_id, weather_dict in data.items():
|
|
318
|
+
results[station_id] = StationWeather.model_validate(weather_dict)
|
|
319
|
+
return results
|
|
320
|
+
|
|
321
|
+
async def infomobilita_rss(self, is_info_lavori: bool = False) -> list[InfomobilitaNews]:
|
|
322
|
+
"""Notizie circolazione o lavori programmati (GET /infomobilitaRSS/{isInfoLavori})."""
|
|
323
|
+
param = "true" if is_info_lavori else "false"
|
|
324
|
+
r = await self._request(f"infomobilitaRSS/{param}")
|
|
325
|
+
if r.status_code == 204 or not r.text.strip():
|
|
326
|
+
return []
|
|
327
|
+
r.raise_for_status()
|
|
328
|
+
return parse_infomobilita_rss(r.text)
|
|
329
|
+
|
|
330
|
+
async def infomobilita_rss_box(
|
|
331
|
+
self, is_info_lavori: bool = False
|
|
332
|
+
) -> list[InfomobilitaNewsHeadline]:
|
|
333
|
+
"""Titoli sintetici delle notizie di infomobilità (GET /infomobilitaRSSBox/{isInfoLavori})."""
|
|
334
|
+
param = "true" if is_info_lavori else "false"
|
|
335
|
+
r = await self._request(f"infomobilitaRSSBox/{param}")
|
|
336
|
+
if r.status_code == 204 or not r.text.strip():
|
|
337
|
+
return []
|
|
338
|
+
r.raise_for_status()
|
|
339
|
+
return parse_infomobilita_rss_box(r.text)
|
|
340
|
+
|
|
341
|
+
async def infomobilita_ticker(self) -> list[str]:
|
|
342
|
+
"""Avvisi brevi del ticker scorrevole (GET /infomobilitaTicker)."""
|
|
343
|
+
r = await self._request("infomobilitaTicker")
|
|
344
|
+
if r.status_code == 204 or not r.text.strip():
|
|
345
|
+
return []
|
|
346
|
+
r.raise_for_status()
|
|
347
|
+
return parse_infomobilita_ticker(r.text)
|
|
348
|
+
|
|
349
|
+
async def language(self, id_lingua: str = "it") -> dict[str, str]:
|
|
350
|
+
"""Dizionario di traduzione dell'interfaccia (GET /language/{idLingua})."""
|
|
351
|
+
r = await self._request(f"language/{quote(id_lingua)}")
|
|
352
|
+
if r.status_code == 204 or not r.text.strip():
|
|
353
|
+
return {}
|
|
354
|
+
r.raise_for_status()
|
|
355
|
+
return r.json()
|