How it works¶
Download once, then only what changed¶
ISPRA rebuilds the inventory files every day
and sends an ETag and a Last-Modified with each one. The library
saves them next to the file and sends them back the next time: the server
answers 304 Not Modified if the copy you have is still current.
sequenceDiagram
autonumber
participant You as Your script
participant C as IdroGEO client
participant D as Download folder
participant S as idrogeo.isprambiente.it
You->>C: fetch(opendata("frane_poly"))
C->>D: read frane_poly_….zip.meta.json
D-->>C: nothing yet
C->>S: GET /opendata/frane/frane_poly_ispra_opendata.zip
S-->>C: 200, ~570 MB, ETag "abc", Last-Modified …
C->>D: stream to .part → rename, save ETag, unpack
C-->>You: Path to the folder
Note over You,S: the next day
You->>C: fetch(opendata("frane_poly"))
C->>D: read meta.json → ETag "abc"
C->>S: GET … If-None-Match: "abc"
S-->>C: 304 Not Modified (no body)
C-->>You: same folder, nothing downloaded
What fetch decides¶
flowchart TD
start(["fetch(dataset, directory)"]) --> m{"file and meta.json<br/>on disk, same URL?"}
m -- no --> g["GET, no conditions"]
m -- yes --> cg["GET with If-None-Match<br/>and If-Modified-Since"]
g --> s{"answer"}
cg --> s
s -- 304 --> u{"unpacked folder<br/>exists?"}
s -- 200 --> w["stream to .part → rename<br/>save ETag / Last-Modified"]
s -- 404 --> e(["IdrogeoError:<br/>layer or area does not exist"])
s -- "429 / 5xx / network" --> r["wait 2 s, 4 s, 8 s…<br/>and try again"] --> s
w --> x["unpack into folder.part<br/>swap it in whole"]
u -- yes --> done(["return the folder"])
u -- no --> x
x --> done
A reader never finds half a file or half a shapefile, and a ZIP member that
would land outside its folder (../../etc/…) is refused.
The building blocks¶
The library has two layers. Use the high-level client, or call the pure functions with any HTTP library you like.
flowchart LR
subgraph yours["Your code"]
H["httpx.AsyncClient<br/>(timeouts, proxy, pooling)"]
end
subgraph lib["ispra_idrogeo"]
direction TB
CL["client.py<br/>IdroGEO · Download"]
SO["sources.py<br/>opendata · mosaic · sensor_data<br/>REGIONS · PROVINCES"]
AP["api.py<br/>summary_path · search_params · parse"]
CA["cache.py<br/>default_directory · validators · extract"]
ER["errors.py<br/>IdrogeoError"]
end
H --> CL
CL --> SO
CL --> AP
CL --> CA
sources.py,api.py: pure functions, no network. They build URLs and requests and read answers. Easy to test, easy to reuse.cache.py: the folder, the.meta.jsonvalidators and the safe unpacking.client.py:IdroGEOwires them to yourhttpx.AsyncClient.